Linux进程管理
waitpid系统调用
用法: #include <sys/types.h> #include <sys/wait.h> pid_t waitpid ( pid_t pid, int *status, int options ); Wait允许父进程等待它所有的子进程.但是如果父进程有特殊的 需要,它可以使用waitpid等待某个特殊的子进程. 第一个参数pid指定父进程想要等待子进程的进程id. 第二个参数status将保存waitpid返回时子进程的状态. 最后一个参数options可以传入多种可选值,这些值都定义在 <sys/wait.h〉中,这些值中最常用的是WNOHANG.它允许waitpid 进入一个循环可检测一个状态,而不会在子进程运行时阻塞调用 进程.如果设置了WNOHANG,则waitpid在子进程未终止时返回 0.
程序输出…? 程序输出 ?
实现简单的进程互斥
Linux中提供了一个文件锁定函数lockf, 可以用来对文件及标准输入输出设备进 行锁定,若某个进程成功锁定某个文件, 则其它进程再试图对同一文件进行锁定 时,就无法成功,该进程也会被阻塞. 因此可以用lockf实现进程间简单的互斥.
没有互斥的程序
Fork中的进程id
例子中pid的值可以用来区分父进程与子进程.在父进 程中,pid被设置为一个非零的正整数,而在子进程中 设置为零.由于在父进程和子进程中返回的pid值不同, 因此程序员可以对两个进程采取特定的,不同的动作. 返回到父进程的pid值,称为子进程的进程id. 系统用这 个值来识别新创建的进程,与使用用户id识别一个用 户非常相像.由此,所有的进程都有fork调用产生,每 个UNIX进程都有自己的进程id,并且在任何时间内都 是唯一的.这样,进程的id可以认为是它的识别签名.
Execl原理示意图
Execl原理示意图
其它的exec调用
其它的exec形式为程序提供了多样的参数构造列表. execv只有两个参数:第一个(前面用法表描述的path) 是一个包含执行陈绪路径的字符串.第二个(argv) 是一个字符串数组:char * const * argv[]; 数组的第一个成员指针通常指向要执行的程序名(不 包括任何路径前缀).剩下的成员指向程序其它的附 加参数.由于这个列表的长度未定,所以它必须以一 个空指针来结束. execlp与execvp几乎等同于execl与execv.主要差别在于: execlp和execvp的第一个参数是一个简单的文件名,而 不是一个路径名.路径前缀将从shell环境变量PATH所 列的目录中进行搜索
没互斥
#include<stdio.h> main() { int pid; pid=fork( ); if (pid==0) {
/*子进程*/
for (i=0;i<500;i++) printf("son%d\n",i); //输出500个字符串son } else if (pid>0) { /*父进程*/
execl
用法: #include <unistd.h> int execl (const char *path, const char *arg0, … , const char *argn, (char *)0); int execlp ( …) int execv ( …) int execvp ( …) Execl的所有参数都是字符指针类型.在用法表中列出的第一个参数path,指定将 被执行的程序的文件名:对execl来讲,它必须是一个有效的路径名,绝对或者相 对的文件路径名.这个文件必须包含一个具有执行权限的真正程序或者是一个 shell脚本.【系统通过查看文件前两个字节左右范围内的数据来判断该文件是否 包含一个程序.如果他们包含一个特殊的数值,叫做幻数(magicnumber),系统 就认为这个文件为一个程序】.第二个参数arg0是程序名或者从路径名元素中剥 离出的命令.这个参数以及剩余的有效参数都会被所调用程序获得,与shell中的 命令行参数相对应.实际上,shell本身在执行命令联合使用了exec和fork.由于 参数列表是任意长度的,因此他需要在列表的最后使用一个空指针来结束.
exec和fork 一起使用
wait系统调用
用法: #include <sys/types.h> #include <sys/wait.h> pid_t wait (int *status ) ); 当子进程运行时wait会暂时将调用进程挂起. 一旦子进程结束了,等待的父进程就可以继续 执行.如果多于一个子进程正在运行,wait将 在父进程的的任意一个子进程退出时返回. Wait经常在fork之后被父进程调用.例如:
for (i=0;i<500;i++) printf("parent%d\n",i); //输出500个字符串parent } else { printf("Fork failed\n"); }e<stdio.h> main() { int pid; pid=fork( ); if (pid==0) { /* /*子进程*/ */ lockf(1,1,0); for (i=0;i<500;i++) printf("son%d\n",i); //输出500个字符串son lockf(1,0,0); } else if (pid>0) { /*父进程*/ lockf(1,1,0); for (i=0;i<500;i++) printf("parent%d\n",i); //输出500个字符串parent lockf(1,0,0); } else { printf("Fork failed\n"); } }
wait系统调用
…Wait用法示例
int status; pid_t cpid; cpid=fork(); //create new process if (cpid==0) { //child } else { //parent cpid=wait(&status); printf("The child %d is dead\n",cpid); } …
execl调用ls程序
/* runls—uses execl to run ls */ execl 调用目录列表程序ls #include <unistd.h> main() { printf("executing ls\n"); execl("/bin/ls","ls","-l",(char *)0); /* If execl returns, the call has failed, so …*/ perror("execl failed to run ls\n"); exit(1); }
Fork原理
这幅图中有两个部分,fork之前与之后.之前的部分 表示在fork调用以前的情况.包括标注为A的单个进程 (只是为了方便而使用标注A,它对系统来说没有任何 意义).以PC(程序计数器)为标注的箭头标识当前 进程执行的语句.因为它指向了第一个printf,所以程 序将在标准输出中输出一条不太重要的消息"One". fork之后的部分就是紧接着fork调用之后的情形.这 里有两个进程A和B一起运行,进程A与图中之前的部分 一样.B是由fork调用产生的新进程.除了pid值这个 主要不同之外,通过图中的三行代码完成复制任务之 后,B就是A的一个副本,并运行着同一个程序.使用 之前介绍的术语,A是父进程而B是子进程.
exec和fork 一起使用
exec和fork结合起来使用,为程序员提 供了一个强有力的工具.在由fork创建 的子进程中使用exec时,一个程序可以 在其子进程中运行另一个程序而不会将 自己覆盖.下面的例程将展示如何进行 这一过程.在本例中,我们还会介绍一 个简单的错误处理例程fatal 和系统调 用wait,该系统调用可使一个进程等待它 的进程,直至完成他所作的事情.
程序输出区别
在有互斥的程序中,由于lockf( )函数锁 定标准输出设备屏幕,故不可能出现一 个字符串序列还没有显示完,就被其他 进程抢占输出的情形;各组字符串的输 出顺序可能不同,但必定连续输出500次.
Exec函数族的功能
所有exec变种都完成同一功能:装卸一个新的程序, 并将之转换到调用进程的内存空间.如果exec调用成 功,调用程序将被新的程序完全覆盖,并且从新程序 的起始开始运行.结果可以认为是一个新的进程,除 了保持与原调用进程相同的进程id. 必须强调的是,exec并不是创建一个新的子进程,以 便与调用进程的同时运行.相反,旧的程序将被新程 序所覆盖.因此,不像fork调用那样,exec在成功调用 之后不会返回. 为简单起见,我们只集中介绍一个exec调用,叫做 execl.
exec和fork 一起使用
/* Runls3—run ls in a subprocess */ #include <unistd.h> main( ) { pid_t pid; switch (pid=fork( )) { case -1: fatal("fork failed"); break; case 0: /* child calls exec */ execl("/bin/ls", "ls", "-l", (char *)0); faltal("exec failed"); break; default: /* parent uses wait to suspend execution until child finishes */ wait((int *)0); printf("is completed\n"); exit(0); } }