操作系统实验指导书(5)

2020-02-22 11:10

操作系统实验指导书

编译并运行: $ cc wait2.c -o wait2 $ ./wait2 This is child process with pid of 1538. the child process 1538 exit normally. the return code is 3. 父进程准确捕捉到了子进程的返回值3,并把它打印了出来。

5、waitpid

waitpid系统调用在Linux函数库中的原型是: #include /* 提供类型pid_t的定义 */ #include pid_t waitpid(pid_t pid,int *status,int options) 从本质上讲,系统调用waitpid和wait的作用是完全相同的,但waitpid多出了两个可由用户控制的参数pid和options:

pid

从参数的名字pid和类型pid_t中就可以看出,这里需要的是一个进程ID。但当pid取不同的值时,在这里有不同的意义。

pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。

pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。

pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。

pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。 什么是进程组?

进程组是一个或多个进程的集合,每个进程组有一个唯一的进程组ID,有一个组长进程。组长进程的进程ID与进程组ID相同。

关于进程组的问题:

系统从init进程开始fork,那么基本上所有进程都是init进程组的。再推下去,同一个进程是可以属于不同的进程组的,他可以在进程组A里为非组长进程,而同时做为进程组B的进程组组长。

options

options提供了一些额外的选项来控制waitpid,目前在Linux中只支持WNOHANG和WUNTRACED两个选项,这是两个常数,可以用\运算符把它们连接起来使用,比如: ret=waitpid(-1,NULL,WNOHANG | WUNTRACED); 如果我们不想使用它们,也可以把options设为0,如: ret=waitpid(-1,NULL,0); 如果使用了WNOHANG参数调用waitpid,即使没有子进程退出,它也会立即返回,不会像wait那样永远等下去。

而WUNTRACED参数,由于涉及到一些跟踪调试方面的知识,加之极少用到,这里就不多费笔墨了。

返回值和错误

waitpid的返回值比wait稍微复杂一些,一共有3种情况: 当正常返回的时候,waitpid返回收集到的子进程的进程ID; 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返

21

操作系统实验指导书

回0;

如果调用中出错,则返回-1;

当pid所指示的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid就会出错返回 /* waitpid.c */ #include #include #include main() { pid_t pc, pr; pc=fork(); } if(pc<0) /* 如果fork出错 */ printf(\else if(pc==0){ sleep(10); exit(0); } /* 如果是父进程 */ do{ pr=waitpid(pc, NULL, WNOHANG); /* 使用了WNOHANG参数,waitpid不会在这里等待 */ if(pr==0){ /* 如果没有收集到子进程 */ printf(\ sleep(1); } /* 没有收集到子进程,就回去继续尝试 */ /* 如果是子进程 */ /* 睡眠10秒 */ }while(pr==0); if(pr==pc) printf(\else printf(\ 编译并运行: $ cc waitpid.c -o waitpid $ ./waitpid No child exited No child exited No child exited No child exited No child exited No child exited No child exited No child exited

22

操作系统实验指导书

No child exited No child exited successfully get child 1526 父进程经过10次失败的尝试之后,终于收集到了退出的子进程。 因为这只是一个例子程序,不便写得太复杂,所以我们就让父进程和子进程分别睡眠了10秒钟和1秒钟,代表它们分别作了10秒钟和1秒钟的工作。父子进程都有工作要做,父进程利用工作的简短间歇察看子进程的是否退出,如退出就收集它。

6、exec

说是exec系统调用,实际上在Linux中,并不存在一个exec()的函数形式,exec指的是一组函数,一共有6个,分别是: #include int execl(const char *path, const char *arg, ...); int execlp(const char *file, const char *arg, ...); int execle(const char *path, const char *arg, ..., char *const envp[]); int execv(const char *path, char *const argv[]); int execvp(const char *file, char *const argv[]); int execve(const char *path, char *const argv[], char *const envp[]); 其中只有execve是真正意义上的系统调用,其它都是在此基础上经过包装的库函数。 exec函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件。这里的可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。

与一般情况不同,exec函数族的函数执行成功后不会返回,因为调用进程的实体,包括代码段,数据段和堆栈等都已经被新的内容取代,只留下进程ID等一些表面上的信息仍保持原样,看上去还是旧的躯壳,却已经注入了新的灵魂。只有调用失败了,它们才会返回一个-1,从原程序的调用点接着往下执行。

每当有进程认为自己不能为系统和拥护做出任何贡献了,他就调用任何一个exec,让自己以新的面貌重生;或者,更普遍的情况是,如果一个进程想执行另一个程序,它就可以fork出一个新进程,然后调用任何一个exec,这样看起来就好像通过执行应用程序而产生了一个新进程一样。

事实上第二种情况被应用得如此普遍,以至于Linux专门为其作了优化,我们已经知道,fork会将调用进程的所有内容原封不动的拷贝到新产生的子进程中去,这些拷贝的动作很消耗时间,而如果fork完之后我们马上就调用exec,这些辛辛苦苦拷贝来的东西又会被立刻抹掉,这看起来非常不划算,于是人们设计了一种\写时拷贝(copy-on-write)\技术,使得fork结束后并不立刻复制父进程的内容,而是到了真正实用的时候才复制,这样如果下一条语句是exec,它就不会白白作无用功了,也就提高了效率。

上面6条函数看起来似乎很复杂,但实际上无论是作用还是用法都非常相似,只有很微小的差别。在学习它们之前,先来了解一下我们习以为常的main函数。 int main(int argc, char *argv[], char *envp[]) 参数argc指出了运行该程序时命令行参数的个数,数组argv存放了所有的命令行参数,数组envp存放了所有的环境变量。环境变量指的是一组值,从用户登录后就一直存在,很多应用程序需要依靠它来确定系统的一些细节,我们最常见的环境变量是PATH,它指出了应到哪里去搜索应用程序,如/bin;HOME也是比较常见的环境变量,它指出了我们在系统中的个人目录。环境变量一般以字符串\的形式存在,XXX表示变量名,xxx表示变量的值。

23

操作系统实验指导书

值得一提的是,argv数组和envp数组存放的都是指向字符串的指针,这两个数组都以一个NULL元素表示数组的结尾。 /* main.c */ int main(int argc, char *argv[], char *envp[]) { printf(\ printf(\ while(*argv) printf(\ printf(\ while(*envp) printf(\ return 0; } 编译它: $ cc main.c -o main $ ./main -xx 000 ### ARGC ### 3 ### ARGV ### ./main -xx 000 ### ENVP ### PWD=/home/lei REMOTEHOST=dt.laser.com HOSTNAME=localhost.localdomain QTDIR=/usr/lib/qt-2.3.1 LESSOPEN=|/usr/bin/lesspipe.sh %s KDEDIR=/usr USER=lei LS_COLORS= MACHTYPE=i386-redhat-linux-gnu MAIL=/var/spool/mail/lei INPUTRC=/etc/inputrc LANG=en_US LOGNAME=lei SHLVL=1 SHELL=/bin/bash HOSTTYPE=i386 OSTYPE=linux-gnu HISTSIZE=1000 24

操作系统实验指导书

TERM=ansi HOME=/home/lei PATH=/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin:/home/lei/bin_=./main int execve(const char *path, char *const argv[], char *const envp[]); 对比一下main函数的完整形式,这两个函数里的argv和envp是完全一一对应的关系。execve第1个参数path是被执行应用程序的完整路径,第2个参数argv就是传给被执行应用程序的命令行参数,第3个参数envp是传给被执行应用程序的环境变量。

留心看一下这6个函数还可以发现,前3个函数都是以execl开头的,后3个都是以execv开头的,它们的区别在于,execv开头的函数是以\这样的形式传递命令行参数,而execl开头的函数采用了我们更容易习惯的方式,把参数一个一个列出来,然后以一个NULL表示结束。这里的NULL的作用和argv数组里的NULL作用是一样的。

在全部6个函数中,只有execle和execve使用了char *envp[]传递环境变量,其它的4个函数都没有这个参数,这并不意味着它们不传递环境变量,这4个函数将把默认的环境变量不做任何修改地传给被执行的应用程序。而execle和execve会用指定的环境变量去替代默认的那些。

还有2个以p结尾的函数execlp和execvp,它们和execl与execv的差别很小,事实也确是如此,除execlp和execvp之外的4个函数都要求,它们的第1个参数path必须是一个完整的路径,如\;而execlp和execvp的第1个参数file可以简单到仅仅是一个文件名,如\,这两个函数可以自动到环境变量PATH制定的目录里去寻找。 /* exec.c */ #include #include main() { char *envp[]={\ \ \ NULL}; char *argv_execv[]={\NULL}; char *argv_execvp[]={\ char *argv_execve[]={\ if(fork()==0) if(execl(\ perror(\ if(fork()==0) if(execlp(\ perror(\ if(fork()==0) if(execle(\ perror(\ if(fork()==0) if(execv(\ 25


操作系统实验指导书(5).doc 将本文的Word文档下载到电脑 下载失败或者文档不完整,请联系客服人员解决!

下一篇:童玲丽 - 图文

相关阅读
本类排行
× 注册会员免费下载(下载后可以自由复制和排版)

马上注册会员

注:下载文档有可能“只有目录或者内容不全”等情况,请下载之前注意辨别,如果您已付费且无法下载或内容有问题,请联系我们协助你处理。
微信: QQ: