免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 50282 | 回复: 125
打印 上一主题 下一主题

UNIX系统编程(10月11号更新 1楼,2楼,11楼,20楼,53楼,54楼) [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2006-11-05 13:57 |只看该作者 |倒序浏览
前言:
前一阵子在网上闲逛,偶然发现了CU这个地方感觉还不错。所以就注册了个ID有事没事来这里看看。慢慢发现这里编程高手云集,但同时也有一些刚学习了一些C,UNIX编程入门级选手。我想起我上大学学习UNIX系统编程时有过一本笔记,我花了两天时间从我书箱里的最下面把它给翻了出来。决定把这本“出土文物”拿出来进行一下“保护性修补”,然后贴出来,如果能给UNIX编程的初学者带来一些帮助的话,也就算这本给这本笔记找到了新的利用价值,反正总比压箱底强吧。
说道UNIX编程的入门书籍,凡是学过的应该很少有人不知道W.Richard Stevens的那本《UNIX环境高级编程》的吧。就像中国学习C语言的人80%以上都知道谭浩强教授一样。W.Richard Steven是国际著名的计算机专家,计算机图书作家。他所写的网络协议和UNIX编程等几部书部部都堪称圣经级读本。可惜的是他英年早逝,实是令人惋惜呀。说远了,我想说的是W.Richard Stevens的那本《UNIX环境高级编程》虽然好,但内容体系对一个刚刚起步的人来说也过于庞大,即使你看它一两百页也很难对UNIX编程有一个整体把握,我的帖子不追求面面俱到,希望能用最简洁的篇幅来让初学者对UNIX编程的主要内容有一个大体的把握,方便以后进阶学习。甚至不用刻意通读那本《UNIX环境高级编程》,用到什么地方在仔细读某一章节,边用边学效果会更好一些,我就是这么干的。呵呵,似乎有点老王卖瓜的意思,嘻嘻。
        最后介绍一下我认为不错的学习工具,一是刚才说的那本《UNIX环境高级编程》,还有一本Kay A.Robbins, Steve Robbins的《UNIX系统编程》,里面例题较多,容易理解。在有就是man命令了,一套庞大的,快速的,免费的,权威的,方便的在线帮助系统。你有什么不会的就输入
  1. $man 你想查询的内容
复制代码

如果man你也不会用也不要紧,那你就
  1. $man man
复制代码
一下,呵呵。只可惜好像还没有翻译成中文,对于像我这样英文水平还有待提高的朋友来说读起来有点麻烦。最后还有一个好工具就是网络,有什么不会的google一下好多问题都能解决。
好,啰嗦的半天,说归正传,免得给大家造成光说不练的印象。

    第一章:什么是系统编程
        UNIX系统编程,简单的说就是“C语言+系统调用(system call)”,学会了C语言再知道一些系统调用的方法,其实就可以进行UNIX系统编程了。那什么又是系统调用呢,其实初学者就把它看当成是函数用就可以了。这些“函数”是干什么用的呢,大家知道操作系统内核管理着我们的计算机资源,比如CPU,内存,硬盘等等。应用程序是无法直接访问到它们的。那我们想利用这些资源怎么办呢,内核就给我们提供了一个接口,我们可以利用这个接口来进行计算机资源的使用。内核也通过接口来判断我们的使用请求是否合法,合法的的提供资源,不合法的给与干掉。就好比是金库,银行和储户。金库里有要多地人民币,这就是资源。然而我们储户却无法直接接触到这些可爱的人民币,因为它们是通过银行来管理的,银行就好比是内核。但我们怎么样才能从金库里取出钱来呢,我们可以去银行窗口办理存款取款手续,这就是系统调用。当然,每个人的取款限额都不一样,大款的存款多,他可以取几百万甚至更多,而我存款少,取出一万块就已经不错了。这就是用户的系统调用权限不同。还有就是银行行长,他对这个金库的权限更大(root),当然他的责任也更大,他的一个错误决定有可能导致银行破产。这事可就大了。还有一种情况,一个在银行里没有什么存款的人,却通过一些技术手段,得到了一个大款的密码甚至是伪装成银行行长,把金库里的钱全提走了,这就是黑客。想想这种感觉你就知道为什么世界上有这么多黑客乐此不疲了。还有一些人没有什么“技术含量”直接“抢银行”,把你的计算机都抱走了。那你只有哭了,金库里的钱丢了,好在“房子”还在呀,这回连“房子”也丢了。依照这个比喻,那木马是什么呢?对,就是你的银行职员里出了内奸了。哈哈。好了不胡扯了,我了这么多例子就是想告诉大家,银行(内核)本身来说还是十分坚固稳定的,问题出在如何通过窗口(系统调用)安全地使用它。这也是学习UNIX系统编程是应该注意的问题。从下一个帖子开始说说说进程(process)和如何生成一个进程。

[ 本帖最后由 naker 于 2006-11-11 18:59 编辑 ]

论坛徽章:
0
2 [报告]
发表于 2006-11-05 17:18 |只看该作者
第二章 进程的生成(1)
    先说说什么是进程?假设你编好了一个程序,在它没有被调用之前,它只是乖乖地躺在你的硬盘上,什么事情都不干。好不容易编出来的不干活,这是我们不能容忍的。所以我们要把它调到内存里,然后通过CPU去执行它。所以说,进程就是一个在执行状态下的程序。
我们可以通过
  1. $ps –e
复制代码

命令来查看一下,计算机里所运行的进程有哪些。
那我们计算机里这么多的进程的又是从哪里来的呢,我们可以通过
  1. $ps –axwf
复制代码

来看到关于进程的一张家谱。其实,系统中的所有进程都是通过另一个进程生成的(除了0号进程以外),如果,A进程生成了B进程,那么可以把A进程叫做父进程,把B进程叫做A进程的子进程。也就是说,UNIX系统所有的进程都与其它进程保持着父子关系。

下面我们来看一个简单的例子
  1. #include<stdio.h>
  2. #include<sys/types.h>
  3. #include<unistd.h>

  4. int main( int argc , char *argv[])
  5. {
  6.         int time;
  7.         time = atoi( argv[1] )*60 ;       //将参数中的分钟换成秒数
  8.         if( fork()==0 )       //生成子进程,括号里是子进程的代码
  9.         {
  10.                 sleep( time );
  11.                 fprintf( stderr , "it is time to alarm!\n");
  12.         }
  13.         return 0;
  14. }
复制代码

执行时输入
  1. $./a.out 2
复制代码

这是一个简单的闹钟程序。你把它执行后,看似系统没有什么反映,其实不是,在后台你已经生成了一个进程,来监视时间。如果你用ps命令查看就能看到它。这时你可以接着干你自己的事情。等到了你设定的时间之后这个进程会提示你时间已经到了。这个程序虽不完善(没有进行输入参数的检查),但可以简单的告诉大家如何生成一个进程。
    为了生成一个新的进程,这里使用了 fork() 这个系统调用。它的作用是将父进程的各个变量的值复制给子进程,也就是说当你调用了fork()的那一刻,系统就为你生成了一个和父进程完全一样的进程。当然我们不想要一个父亲一样的孩子,孩子要有自己的个性,那我们如何来赋予孩子自己的个性呢?让我们先来看看fork()这个系统调用的概要。
  1. 头文件       #include <sys/types.h>
  2.                     #include <unistd.h>
  3. 形式       pid_t fork(void);
  4. 返回值          成功时:        父进程中:子进程的进程号(>0)
  5.                                 子进程中:=0
  6.                    失败时:        -1
复制代码
                        
根据上面fork()的特性,我们可以通过fork()的返回值区分父进程要做的事和子进程要做的事。例如,
  1. pid_t pid ;
  2. pid=fork();
  3. if( !pid)
  4. {
  5.         //子进程要做的事
  6. }else if(pid >0)
  7. {
  8.        //子进程生成失败时,父进程要做的事
  9. }else         //pid<0
  10. {
  11.         //子进程生成失败时,父进程要做的事
  12. }
复制代码


好,我们现在已经学会了生成一个子进程了。但它还是遗传了许多父进程的特性,有可能大家会想,能不能用我们生成的子进程来执行另一个与父进程没有任何关系的程序呢。当然是可以的,比如我们常说的shell就是就是这个样子。shell本身也是一个进程当你输入命令回车以后,shell会生成一个子进程来执行你的命令,这条命令可以和shell没有丝毫关系。为了更好的说明问题我们先来做一个简单的shell。当然是最简单的那种。
       
  1. #include<stdio.h>
  2. #include<sys/types.h>
  3. #include<unistd.h>
  4. int main()
  5. {
  6.         static      char prompt[64]="> ";
  7.         char    command[256];
  8.         int   st;
  9.         fprintf(stderr,"%s",prompt);    //  屏幕上的输出提示符
  10.         while(gets(command)!=NULL)  //  取得键盘输入
  11.         {
  12.                 if(fork()==0)     //  生成子进程
  13.                 {         //  子进程要做的事
  14.                        execl(command,command,(char *)0)==-1        //执行所输入的命令
  15.                 }
  16.                 else
  17.                 {         //  父进程要做的事
  18.                         wait(&st);  //  等待子进程结束
  19.                         fprintf(stderr,"%s",prompt);  //  输出提示符,等待命令
  20.                  }
  21.         }
  22.         return 0;
  23. }
复制代码

好了我们保存,编译,执行以下看看
  1. $./a.out
  2. >/bin/ls  //这里必须输入命令的完全路径
  3.         当前目录下文件名
  4. >Ctrl+D 退出程序
  5. $
复制代码

这样我们的一个最初级shell就做好了。虽然它还很弱,还有着安全上的漏洞(使用了gets()),甚至连自己退出都不能,但起码可以让我们看到一个shell是如何执行的了。其实,一个复杂的shell最基本的东西也就使这些。大家要是有兴趣的话可以将gets()换掉,再加上退出功能。

我们再说说程序中出现的一个新的函数execl()。其实它是exec函数组中的一个。这组函数有:
  1. int   execl( path , arg0 , arg1 , ... , argn , (char *)0 );
  2. int   execv( path , argv );
  3. int   execle( path , arg0 , arg1 , ... , argn , (char *)0 , envp );
  4. int   execve( path , argv , envp );
  5. int   execlp( file , arg0 , arg1 , ... , argn , (char *)0 );
  6. int   execvp( file , argv );
复制代码
  1. 参数定义如下:
  2. char *path;
  3. char *file;
  4. char *arg0 , *arg1 , ... , *argn;
  5. char *argv[];
  6. char *envp[];
  7. 返回值:        成功时:所执行的命令将会覆盖现有的进程,所以无返回值
  8.                         失败时:-1
复制代码

比如说我们在shell里执行
  1. $ /bin/ls –l
复制代码

这个命令,实际上shell调用的是
  1. execl( "/bin/ls" , "/bin/ls" , "-l"  , (char *)0 );
复制代码

的一个系统调用
这个函数组函数有6个,用法就不一一说明了,大家可以参看一下其它资料。这里只告诉大家它们的用处,exec函数组就是用来调用一个可执行程序。还有一点很重要,一但进程调用了exec函数那么写在exec函数后面的进程代码将会被覆盖,变成无效的代码了。
例如下面一段代码,我们想在execl执行后输出一段文字列,这是办不到的。
  1. if(fork()==0)     //  生成子进程
  2.                 {         //  子进程要做的事
  3.                        execl(command,command,(char *)0)==-1        //执行所输入的命令
  4.         fprinf(stderr,"lalalalalalalalala!");  //
  5.                 }
  6.                 else
  7.                 {         //  父进程要做的事
  8.                         wait(&st);  //  等待子进程结束
  9.                         fprintf(stderr,"%s",prompt);  //  输出提示符,等待命令
  10.                  }
复制代码

还有一个函数wait(),它的概要是
  1. #include <sys/types.h>
  2. pid_t wait(int *status);
复制代码

返回值就是子进程的进程号
它的参数是个指针。C语言里讲过,一个函数想有一个以上的返回值时,你可以将想返回的变量的地址作为函数的参数。比如说将数组地址作为函数的参数等等。其实这里的status就是这个道理,它的值与子进程的结束方式有关系。当你的子进程以exit()方式结束的话,status所指向的地址的前8位将会是exit()的参数的后8位,而status所指向的地址的后8位是0。例如子进程是exit(1);那status所指向的地址的内容应该是0000 0001 0000 0000。还有如果子进程是通过信号(signal)终止的(信号我们以后再讲),那么我们也可以通过status的值来判断是哪一个信号终止了这个子进程。(详见man)
我们为什么还要在父进程中调用wait(),这涉及到进程状态的概念,我们稍候再说。

[ 本帖最后由 naker 于 2006-11-7 01:08 编辑 ]

论坛徽章:
0
3 [报告]
发表于 2006-11-06 08:59 |只看该作者
这个好,谢谢了

论坛徽章:
0
4 [报告]
发表于 2006-11-06 09:19 |只看该作者
适当温习一下,不错,继续!

论坛徽章:
0
5 [报告]
发表于 2006-11-06 09:23 |只看该作者
支持楼主,继续完善哈。

论坛徽章:
0
6 [报告]
发表于 2006-11-06 09:23 |只看该作者
描述得挺细致,支持了!

论坛徽章:
0
7 [报告]
发表于 2006-11-06 16:27 |只看该作者
怎么不加精呢

论坛徽章:
0
8 [报告]
发表于 2006-11-06 17:35 |只看该作者

好,支持!~

好,支持!~

论坛徽章:
0
9 [报告]
发表于 2006-11-06 22:13 |只看该作者
支持,顶

论坛徽章:
0
10 [报告]
发表于 2006-11-06 22:23 |只看该作者
似乎 0 号进程是内核“生”出来的,1 号 2 号什么的要看操作系统的具体实现了。特别的,在 OpenBSD 上,pid 是伪随机的,几乎不可预测。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP