免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
楼主: naker
打印 上一主题 下一主题

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

论坛徽章:
0
11 [报告]
发表于 2006-11-07 01:06 |只看该作者
明天本来我要把这段期间做的一个小东西,给我们部门的人展示一下,结果晚上8点钟测试机OVER了,恢复它到了晚上11点才回来,太累了。本来今天不想写了,没想到有这么多人支持,眼泪哗哗的,再累也不觉得了!呵呵,废话少说,捞干的!

接着上次那个shell,我们再改进一下它,作个“强化版”。其实是打肿脸充胖子,如果说bash是宝马7系(我今生奋斗的目标)的话,前面我们那个也就是台破自行车,速度不行,还没闸(无法自己退出),还容易出车祸(有安全隐患)。但好歹也是辆车,它能上路也能跑。今天我们给前后胎打点气,让它再提提速。(火车服务那么差,还天天提速呢,咱自行车咋了?)

我们知道,每个UNIX用户都有自己的环境变量。比如$HOME,$SHELL,$PAHT啦等等。这里不多说这个了,不太了解的朋友可以看看别的关于shell的东西。我只说一点如果你是bash或csh的话输入
  1. $env
复制代码

就可以看到你的所有环境变量了。有人可能要问,这不是全部吧,怎么没看到PS1,PS2什么的呀。注意环境变量和shell变量是两码事,它们是shell变量。你也可以输入
  1. $set
复制代码

的话可以看到你所有的环境变量和shell变量。这里面就有它们了吧。

接下来我们先做一个读取环境变量并打印出来的程序
  1. #include<stdio.h>
  2. int main ( int argc , char *argv[] , char *envp[] )
  3. {
  4.         int   i;
  5.         for( i=0 ; envp[i]!=NULL ; i++ )
  6.         {
  7.                 printf( "%s\n" , envp[i] );
  8.         }
  9.         return 0;
  10. }
复制代码

编译,执行
  1. $./a.out
  2. TERM=xterm
  3. SHELL=/bin/bash
  4. HISTSIZE=1000
  5. SSH_CLIENT=192.168.1.10 2115 22
  6. QTDIR=/usr/lib/qt-3.3
  7. QTINC=/usr/lib/qt-3.3/include
  8. SSH_TTY=/dev/pts/1
  9. DISPLAY=:0.0
  10. .
  11. .

  12. 以及你的系统中其它的环境变量。
  13. .
复制代码

  再看看我们上面说的那个$env命令,看看结果是不是一样的。10行不到的东西就能搞定复杂的环境变量是不是很神奇。想一想它是怎么运行的:当输入./a.out并回车,shell就fork一个子进程,子进程去调用exec执行./a.out,并把各个环境变量存到char *envp[]里面去。其实这候,子进程调用的就是上一次我们所说的那个int   execve( path , argv , envp )。我们这个程序是在bash或是csh什么上进行的。所以这回你相信我们上次那个自制的shell原理和那些非常牛的bash,csh是一样一样的了吧。所以我们也可以自豪地说:有什么的啊,我们的自行车不就是比宝马少俩轮吗?在轮子都是圆的这方面,原理是和宝马一样的。呵呵,想宝马想疯了。
  这回我们给我们的shell加点功能,让它能够带上参数。
  1. #include <stdio.h>
  2. #include <string.h>

  3. void getarg( char *argv[] , char *p );      
  4. int main()
  5. {
  6.         static      char    prompt[64]="> ";
  7.         char    command[256],     *argv[256],   *p;
  8.         int   st;
  9.         fprintf( stderr , "%s" , prompt );
  10.         while( (p=gets(command))!=NULL )
  11.         {
  12.                getarg( argv , p );
  13.                 if( fork()==0 )
  14.                 {
  15.                         execv(argv[0],argv);
  16.                 }
  17.                 else
  18.                 {
  19.                         wait( &st );
  20.                         fprintf( stderr , "%s" , prompt);
  21.                 }
  22.         }
  23.         return 0;
  24. }
  25. void getarg( char *argv[] , char *buf )
  26. {
  27.         int i = 1;
  28.         argv[0] = strtok(buf," ");
  29.         while((argv[i] = strtok(NULL," ")) != NULL)
  30.                 i++;
  31. }
复制代码

这回编译再试试看看是不是可以带参数了。
这个程序我不给注释了,参照上次那个程序相信大家能看懂吧。说一下里面的一个新函数strtok()
  1. #include <string.h>
  2. char *strtok(char *str, const char *delim);
复制代码

作用是将文字列str,按标记delim分段,delim可以是多个字符。使用方法:第一次调用时第一个参数是str本身,以后在调用第一个参数设成NULL。返回值就是分割后的每段段首的地址指针。举个例子,
  1. #include        <stdio.h>
  2. #include        <string.h>

  3. int main(void)
  4. {
  5.         char str[] = "ABCD ef.1234.G";
  6.         char *tp;
  7.         tp = strtok( str, " ." ); //分段符设为空格和点
  8.         puts( tp );
  9.         while ( tp != NULL ) {
  10.                 tp = strtok( NULL," ." );
  11.                 if ( tp != NULL ) puts( tp );
  12.         }
  13.         
  14.         return 0
  15. }
复制代码
;
运行结果:
  1. ABCD
  2. ef
  3. 1234
  4. G
复制代码

怎么样?一目了然,不用再多说明了吧。

好,我们的shell就先到这里了,毕竟我们为了入门嘛。我只是起了一个头,希望有兴趣的朋友自己可以在机器想些其它的有趣功能编着玩玩。当年我记得我们还做过,自动检查命令path的,可查询命令历史纪录的,显示子进程号的等等。“自行车”也做得像模像样的,可惜毕业时觉得也没什么用,就都留在学校机房的solaris里了,现在估计早被人删了。今天就说这么多,下次我准备说说关于进程状态。之后就开始说第二部分文件系统。

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

论坛徽章:
0
12 [报告]
发表于 2006-11-07 10:46 |只看该作者
楼主写得很好啊,而且行文轻快幽默。
呵呵,可以出本《深入浅出unix系统编程》的书了。

论坛徽章:
0
13 [报告]
发表于 2006-11-07 12:07 |只看该作者
看楼主的文可作为一种复习和提高

论坛徽章:
0
14 [报告]
发表于 2006-11-07 12:47 |只看该作者
写得很有趣嘛,支持

论坛徽章:
0
15 [报告]
发表于 2006-11-07 12:50 |只看该作者
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
        static      char prompt[64]="> ";
        char    command[256];
        int   st;
        fprintf(stderr,"%s",prompt);    //  屏幕上的输出提示符
        while(gets(command)!=NULL)  //  取得键盘输入
        {
                if(fork()==0)     //  生成子进程
                {         //  子进程要做的事
                       execl(command,command,(char *)0)==-1        //执行所输入的命令
                }
                else
                {         //  父进程要做的事
                        wait(&st);  //  等待子进程结束
                        fprintf(stderr,"%s",prompt);  //  输出提示符,等待命令
                 }
        }
        return 0;
}




fork有个问题一直不明白,就是在循环中调用fork函数,在for中调用时,如果不加exit函数,会产生递归调用,产生第三层或更高层的子进程,在while中没有试过。

楼主的程序会不会产生同样的问题?
谢谢!!

论坛徽章:
0
16 [报告]
发表于 2006-11-07 16:49 |只看该作者
今天刚好看到进程这儿。大有收获啊!
谢谢楼主!

论坛徽章:
0
17 [报告]
发表于 2006-11-07 19:25 |只看该作者
fork有个问题一直不明白,就是在循环中调用fork函数,在for中调用时,如果不加exit函数,会产生递归调用,产生第三层或更高层的子进程,在while中没有试过。

楼主的程序会不会产生同样的问题?
谢谢!!

应该是不会的,别看没有exit,但我调用的是exec。你看了我上面的帖子就知道了,exec把它后面的东西都覆盖掉了,就算写了也白写,这是一。二,本质上说exec又被你调动的程序代替,所以不用担心。程序都写好了,你可以粘贴一下,自己跑跑试试看啊。我觉得自己动手试一试会比光读代码理解的深刻。
另外说一下递归调用,你说的那个for会产生递归调用,我不知道你说的情况时什么样的,不如你把代码写下来大家分析分析。但听你说的这个意思,好像是父进程没有wait().不是递归调用而是父进程没做好计划生育。你用ps axwf看看它们到底是谁的孩子不就行了。

论坛徽章:
0
18 [报告]
发表于 2006-11-07 19:40 |只看该作者
我想问楼主一个问题,是不是每一个创建的子进程都必须在使用完,由父进程调用wait函数来的到该进程的终止状态,释放资源。
是不是必须的?

论坛徽章:
0
19 [报告]
发表于 2006-11-07 20:20 |只看该作者
原帖由 naker 于 2006-11-7 19:25 发表

应该是不会的,别看没有exit,但我调用的是exec。你看了我上面的帖子就知道了,exec把它后面的东西都覆盖掉了,就算写了也白写,这是一。二,本质上说exec又被你调动的程序代替,所以不用担心。程序都写好了,你 ...




程序如下:
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
        pid_t pid;
        int i;

        for(i=0;i<3;i++)
        {
                if( (pid=fork()) < 0)
                        fprintf(stdout, "fork error on %d\n",i);
                else if( pid > 0)
                        printf("parent fork  %d\n",i);
                else
                        
                        printf("child fork %d\n", i);
        }
        
        exit(0);
}
执行结果:
parent fork  0
child fork 0
parent fork  1
parent fork  1
child fork 1
parent fork  2
parent fork  2
parent fork  2
child fork 1
parent fork  2
child fork 2
child fork 2
child fork 2
child fork 2
初步分析:
第一层循环3次i=0,1,2)
parent fork  0
parent fork  1
parent fork  2
child fork 0(进程1,初始i=0)
child fork 1(进程2,初始i=1)
child fork 2(进程3,初始i=2)
第二层进程1循环2次i=1,2)
parent fork  1
parent fork  2
child fork 1(进程4,初始i=1)
child fork 2(进程5,初始i=2)
第二层进程2循环1次i=2)
parent fork  2
child fork 2(进程6,初始i=2)
第三层进程4循环1次i=2)
parent fork  2
child fork 2(进程7,初始i=2)



这个问题不知道是不是你所说的没有等待的原因,我现在没有环境,所以没试验!
如果在之进程中写exit的话,程序就不会产生孙子进程了。
另外还有就是:

int main()
{
        pid_t pid;
        int i = 0;
        printf("asfsda\n");
     for(i=0;i<3;i++)
        while(1)        
        {      
                if( (pid=fork()) < 0)
                        fprintf(stdout, "fork error on %d\n",i);
                else if( pid > 0)
                        printf("parent fork  %d\n",i);
                else   
                {      
                    printf("child fork %d\n", i);
                  //exit(0);   //不明白的地方
                }      
        }      
        
        return(0);
}
(1)加exit(0);结果:
asfsda
parent fork  0
parent fork  1
child fork 0
child fork 1
child fork 2
parent fork  2
不加exit(0);结果:
asfsda
parent fork  0
parent fork  1
parent fork  2
child fork 0
parent fork  1
child fork 1
parent fork  2
child fork 1
parent fork  2
parent fork  2
child fork 2
child fork 2
child fork 2
child fork 2

这里我就不明白了,为什么子进程没有执行printf("asfsda\n");而是从for开始执行呢?是fork函数对循环作特殊处理了吗?


你说exec后面都覆盖掉了,但是如上面的程序,如果没加exit,也没有exec函数,怎么printf("asfsda\n");在孙子进程中就没有执行呢?

能解答一下吗?
谢谢!!

论坛徽章:
0
20 [报告]
发表于 2006-11-07 21:18 |只看该作者
今天我们来说说进程的状态。
虽然说,UNIX看上去好像可以同时运行多个进程,实际上一个CPU同一个时间里只能处理一个进程。然而,CPU速度飞快的在各个进程中穿梭,轮流地执行每一个进程。所以在宏观上看起来像是所有进程一起运行。但微观上,某一个时刻你的计算机只能有一个进程在被CPU处理。当然,这是一个CPU的情况下,多个CPU时涉及到原子操作等等概念,说起来就比较复杂了,有兴趣的朋友可以看看关于内核的资料。回刚才的话,这个正在被处理的进程状态就是执行态,而其它排队等待被执行的进程就是就绪态,还有一些进程自己还没准备好,比如等你的键盘输入等等,这样的进程它们不参加就绪态的排队,而是等准备好了再去排队这叫休眠态。还有全部执行完了还没有被回收的进程,也有一个叫法我们一会说。
举个例子,CPU与进程的关系好像就是象棋大师同时与很多象棋爱好者下棋一样。每个象棋爱好者都有自己的一盘棋,好比进程。象棋大师就是CPU,同时要处理很多盘棋。因为他非常牛,所以轮流到每个棋手前走上一步,那他正走的这盘棋就是执行态,其它已经走完自己的棋等大师过来走棋的就是就绪态,还有一些棋手有可能还在想他自己怎么走那大师没有必要过来理你,直到等他自己走完了下一次大师过来才会理他这就是休眠态。其实大师也是人不可能有那么好的记忆力,经常是走完了你的棋,走下一个人的棋的时候就把你的棋给忘了,那他怎么回来还能接着走呢,因为你的棋盘上保留的就是上次走过的原样,大师再看一遍就想起来了。这就是上下文切换。还有些人已经下输了,大师也不会再过来了。可难得有和大师交流的机会,这些人占着椅子(资源)在那里不走,这就是我们刚才说的那个全部执行完了还没有被回收的进程。为什么它的状态名这么难以启齿呢。因为我总觉得它翻译的太难听了。英语叫zombie,翻译过来叫僵死态或僵尸态,太恐怖了。所以我们还是叫他的英语zombie吧。计算机起源于西方,所以其计算机文化也就带有西方特色,有许多词英语听上去还挺好听甚至富有幽默感,但用中文说味道就不太一样了。比如,bug,daemon了等等。翻译过来就成了漏洞,精灵进程,很正统很严肃。反正原意也已经变了,为什么就不能体现一些有中国特色的幽默呢。比如bug可以叫一小撮隐藏在人民内部的破坏分子。daemon叫雷锋进程,因为它在后面默默无闻的奉献,但很少有人知道它们的存在。
不说了,扯远了。我们再说两句zombie。zombie一般情况下是没有用处的垃圾,所以一个残留zombie的程序不是一个好程序,甚至有安全漏洞。你想你的电脑里有许多的僵尸那是一件多恐怖的事呀。但也有一些高手专门留下zombie有用,不过那是高手的事情了,初学阶段就可以认为zombie没用,等你成了高手在考虑它有什么用也不迟。
用fork()生成子进程,子进程结束的时。要给父进程送去发一个信号(信号以后讲),然后转为zombie状态。当父进程收到了这个信号,就会来收回它孩子的进程列表,这就是父进程中wait()的另一个作用,子进程这时才算真正结束。如果父进程先于子进程结束,那么这些孤儿就会由init来收养,料理它们的后事。
我们用一个程序来证明这一点:
  1. #include<stdio.h>
  2. int main()
  3. {
  4.         int st;
  5.         if( fork()==0 )
  6.         {
  7.                 exit(1);        //子进程结束
  8.         }
  9.         else
  10.         {
  11.                 sleep( 300 );     //父进程休眠300秒
  12.                 wait( &st );
  13.                 printf("Return code=%d\n",i);
  14.         }
  15.         return 0;
  16. }
复制代码

编译,执行,看看是不是有一个 zombie,这是因为它父亲还没睡醒,没来得及处理它的后事呢。

好,进程我们就说到这里。下一次开始我们说说文件系统。
剩下的内容还有,
标准输入输出及管道
信号
共有内存
消息队列
信号量

感谢大家对我的支持,希望大家继续支持我,你们是我发帖的原动力。

[ 本帖最后由 naker 于 2006-11-7 23:30 编辑 ]
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

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

清除 Cookies - ChinaUnix - Archiver - WAP - TOP