📄 program-multi-fork.html
字号:
<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<meta name="Author" content="Edward Fu">
<meta name="GENERATOR" content="Mozilla/4.05 [zh-CN] (X11; I; Linux 2.1.127 i686) [Netscape]">
<title>Freesoft Linux FAQ -- 多进程编程</title>
</head>
<body>
<br>多进程编程
<p>写在前面的话
<br>本文主要根据本人在UNIX系统上的编程实践经验总结而成, 既做为自己在
<br>一个时期内编程实践的部分总结, 又可成为文章发表. 对UNIX程序员初学者来
<br>说是一个小小的经验, 仅供参考; 对UNIX老手来说则不值一哂, 请各位多多指
<br>教.
<p>一.多进程程序的特点
<br>由于UNIX系统是分时多用户系统, CPU按时间片分配给各个用户使用, 而在
<br>实质上应该说CPU按时间片分配给各个进程使用, 每个进程都有自己的运行环境
<br>以使得在CPU做进程切换时不会"忘记"该进程已计算了一半的"半成品". 以DOS
<br>的概念来说, 进程的切换都是一次"DOS中断"处理过程, 包括三个层次:
<br>(1)用户数据的保存: 包括正文段(TEXT), 数据段(DATA,BSS), 栈段
<br>(STACK), 共享内存段(SHARED MEMORY)的保存.
<br>(2)寄存器数据的保存: 包括PC(program counter,指向下一条要执行的指
<br>令的地址), PSW(processor status word,处理机状态字), SP(stack
<br>pointer,栈指针), PCBP(pointer of process control block,进程控
<br>制块指针), FP(frame pointer,指向栈中一个函数的local变量的首地
<br>址), AP(augument pointer,指向栈中函数调用的实参位置), ISP(
<br>interrupt stack pointer,中断栈指针), 以及其他的通用寄存器等.
<br>(3)系统层次的保存: 包括proc,u,虚拟存储空间管理表格,中断处理栈.
<br>以便于该进程再一次得到CPU时间片时能正常运行下去.
<br>既然系统已经处理好所有这些中断处理的过程, 我们做程序还有什么要担
<br>心的呢? 我们尽可以使用系统提供的多进程的特点, 让几个程序精诚合作, 简
<br>单而又高效地把结果给它搞出来.
<br>另外,UNIX系统本身也是用C语言写的多进程程序,多进程编程是UNIX的特
<br>点,当我们熟悉了多进程编程后,将会对UNIX系统机制有一个较深的认识.
<br>首先我介绍一下多进程程序的一些突出的特点:
<br>1.并行化
<br>一件复杂的事件是可以分解成若干个简单事件来解决的, 这在程序员
<br>的大脑中早就形成了这种概念, 首先将问题分解成一个个小问题, 将小问
<br>题再细分, 最后在一个合适的规模上做成一个函数. 在软件工程中也是这
<br>么说的. 如果我们以图的方式来思考, 一些小问题的计算是可以互不干扰
<br>的, 可以同时处理, 而在关键点则需要统一在一个地方来处理, 这样程序
<br>的运行就是并行的, 至少从人的时间观念上来说是这样的. 而每个小问题
<br>的计算又是较简单的.
<br>2.简单有序
<br>这样的程序对程序员来说不亚于管理一班人, 程序员为每个进程设计
<br>好相应的功能, 并通过一定的通讯机制将它们有机地结合在一起, 对每个
<br>进程的设计是简单的, 只在总控部分小心应付(其实也是蛮简单的), 就可
<br>完成整个程序的施工.
<br>3.互不干扰
<br>这个特点是操作系统的特点, 各个进程是独立的, 不会串位.
<br>4.事务化
<br>比如在一个数据电话查询系统中, 将程序设计成一个进程只处理一次
<br>查询即可, 即完成一个事务. 当电话查询开始时, 产生这样一个进程对付
<br>这次查询; 另一个电话进来时, 主控程序又产生一个这样的进程对付, 每
<br>个进程完成查询任务后消失. 这样的编程多简单, 只要做一次查询的程序
<br>就可以了.
<p>二.常用的多进程编程的系统调用
<br>1.fork()
<br>功能:创建一个新的进程.
<br>语法:#include
<br>#include
<br>pid_t fork();
<br>说明:本系统调用产生一个新的进程, 叫子进程, 是调用进程的一个复
<br>制品. 调用进程叫父进程, 子进程继承了父进程的几乎所有的属
<br>性:
<br>. 实际UID,GID和有效UID,GID.
<br>. 环境变量.
<br>. 附加GID.
<br>. 调用exec()时的关闭标志.
<br>. UID设置模式比特位.
<br>. GID设置模式比特位.
<br>. 进程组号.
<br>. 会话ID.
<br>. 控制终端.
<br>. 当前工作目录.
<br>. 根目录.
<br>. 文件创建掩码UMASK.
<br>. 文件长度限制ULIMIT.
<br>. 预定值, 如优先级和任何其他的进程预定参数, 根据种类不同
<br>决定是否可以继承.
<br>. 还有一些其它属性.
<br>但子进程也有与父进程不同的属性:
<br>. 进程号, 子进程号不同与任何一个活动的进程组号.
<br>. 父进程号.
<br>. 子进程继承父进程的文件描述符或流时, 具有自己的一个拷贝
<br>并且与父进程和其它子进程共享该资源.
<br>. 子进程的用户时间和系统时间被初始化为0.
<br>. 子进程的超时时钟设置为0.
<br>. 子进程的信号处理函数指针组置为空.
<br>. 子进程不继承父进程的记录锁.
<br>返回值: 调用成功则对子进程返回0, 对父进程返回子进程号, 这也是
<br>最方便的区分父子进程的方法. 若调用失败则返回-1给父进程,
<br>子进程不生成.
<br>例子:pid_t pid;
<br>if ((pid=fork())>0) {
<br>/*父进程处理过程*/
<br>}
<br>else if (pid==0) {
<br>/*子进程处理过程*/
<br>exit(0); /*注意子进程必须用exit()退出运行*/
<br>}
<br>else {
<br>printf("fork error\n");
<br>exit(0);
<br>}
<br>2.system()
<br>功能:产生一个新的进程, 子进程执行指定的命令.
<br>语法:#include
<br>#include
<br>int system(string)
<br>char *string;
<br>说明:本调用将参数string传递给一个命令解释器(一般为sh)执行, 即
<br>string被解释为一条命令, 由sh执行该命令.若参数string为一
<br>个空指针则为检查命令解释器是否存在.
<br>该命令可以同命令行命令相同形式, 但由于命令做为一个参数放
<br>在系统调用中, 应注意编译时对特殊意义字符的处理. 命令的查
<br>找是按PATH环境变量的定义的. 命令所生成的后果一般不会对父
<br>进程造成影响.
<br>返回值:当参数为空指针时, 只有当命令解释器有效时返回值为非零.
<br>若参数不为空指针, 返回值为该命令的返回状态(同waitpid())
<br>的返回值. 命令无效或语法错误则返回非零值,所执行的命令被
<br>终止. 其他情况则返回-1.
<br>例子:char command[81];
<br>int i;
<br>for (i=1;i<8;i++) {
<br>sprintf(command,"ps -t tty%02i",i);
<br>system(command);
<br>}
<br>3.exec()
<br>功能:执行一个文件
<br>语法:#include
<br>int execl(path,arg0,...,argn,(char*)0)
<br>char *path,*arg0,...,*argn;
<p>int execv(path,argv)
<br>char *path,*argv[];
<p>int execle(path,arg0,...,argn,(char*)0,envp)
<br>char *path,*arg0,...,*argn,*envp[];
<p>int execve(path,argv,envp)
<br>char *path,*argv[],*envp[];
<p>int execvp(file,argv)
<br>char *file,*argv[];
<br>说明:这是一个系统调用族, 用于将一个新的程序调入本进程所占的内
<br>存, 并覆盖之, 产生新的内存进程映象. 新的程序可以是可执行
<br>文件或SHELL批命令.
<br>当C程序被执行时,是如下调用的:
<br>main(int argc,char *argv[],char *envp[]);
<br>argc是参数个数,是各个参数字符串指针数组,envp是新进程的环
<br>境变量字符串的指针数组.argc至少为1,argv[0]为程序文件名,
<br>所以,在上面的exec系统调用族中,path为新进程文件的路径名,
<br>file为新进程文件名,若file不是全路径名,系统调用会按PATH环
<br>境变量自动找对应的可执行文件运行.若新进程文件不是一个可
<br>执行的目标文件(如批处理文件),则execlp()和execvp()会将该
<br>文件内容作为一个命令解释器的标准输入形成system().
<br>arg0,...等指针指向'\0'结束的字符串,组成新进程的有效参数,
<br>且该参数列表以一个空指针结束.反过来,arg0至少必须存在并指
<br>向新进程文件名或路径名.
<br>同样,argv是字符串指针数组,argv[0]指向新进程文件名或路径
<br>名,并以一空指针结束.
<br>envp是一个字符串指针数组,以空指针结束,这些字符串组成新进
<br>程的环境.
<br>在调用这些系统调用前打开的文件指针对新进程来说也是打开的,
<br>除非它已定义了close-on-exec标志.打开的文件指针在新进程中
<br>保持不变,所有相关的文件锁也被保留.
<br>调用进程设置并正被捕俘的信号在新进程中被恢复为缺省设置,
<br>其它的则保持不变.
<br>新进程启动时按文件的SUID和SGID设置定义文件的UID和GID为有
<br>效UID和GID.
<br>新进程还继承了如下属性:
<br>. 附加GID.
<br>. 进程号.
<br>. 父进程号.
<br>. 进程组号.
<br>. 会话号.
<br>. 控制终端.
<br>. alarm时钟信号剩下的时间.
<br>. 当前工作目录.
<br>. 根目录.
<br>. 文件创建掩码.
<br>. 资源限制.
<br>. 用户时间,系统时间,子进程用户时间,子进程系统时间.
<br>. 记录锁.
<br>. 进程信号掩码.
<br>. 信号屏蔽.
<br>. 优先级.
<br>. 预定值.
<br>调用成功后,系统调用修改新进程文件的最新访问时间.
<br>返回值:该系统调用一般不会有成功返回值, 因为原来的进程已荡然无
<br>存.
<br>例子:printf("now this process will be ps command\n");
<br>execl("/bin/ps","ps","-ef",NULL);
<br>4.popen()
<br>功能:初始化从/到一个进程的管道.
<br>语法:#include
<br>FILE *popen(command,type)
<br>char *command,type;
<br>说明:本系统调用在调用进程和被执行命令间创建一个管道.
<br>参数command做为被执行的命令行.type做为I/O模式,"r"为从被
<br>执行命令读,"w"为向被执行命令写.返回一个标准流指针,做为管
<br>道描述符,向被执行命令读或写数据(做为被执行命令的STDIN或
<br>STDOUT)该系统调用可以用来在程序中调用系统命令,并取得命令
<br>的输出信息或者向命令输入信息.
<br>返回值:不成功则返回NULL,成功则返回管道的文件指针.
<br>5.pclose()
<br>功能:关闭到一个进程的管道.
<br>语法:#include
<br>int pclose(strm)
<br>FILE *strm;
<br>说明:本系统调用用于关闭由popen()打开的管道,并会等待由popen()
<br>激活的命令执行结束后,关闭管道后读取命令返回码.
<br>返回值:若关闭的文件描述符不是由popen()打开的,则返回-1.
<br>例子:printf("now this process will call popen system call\n");
<br>FILE * fd;
<br>if ((fd=popen("ps -ef","r"))==NULL) {
<br>printf("call popen failed\n");
<br>return;
<br>}
<br>else {
<br>char str[80];
<br>while (fgets(str,80,fd)!=NULL)
<br>printf("%s\n",str);
<br>}
<br>pclose(fd);
<br>6.wait()
<br>功能:等待一个子进程返回并修改状态
<br>语法:#include
<br>#include
<br>pid_t wait(stat_loc)
<br>int *stat_loc;
<br>说明:允许调用进程取得子进程的状态信息.调用进程将会挂起直到其
<br>一个子进程终止.
<br>返回值:等待到一个子进程返回时,返回值为该子进程号,否则返回值为
<br>-1.同时stat_loc返回子进程的返回值.
<br>例子:/*父进程*/
<br>if (fork()>0) {
<br>wait((int *)0);
<br>/*父进程等待子进程的返回*/
<br>}
<br>else {
<br>/*子进程处理过程*/
<br>exit(0);
<br>}
<br>7.waitpid()
<br>功能:等待指定进程号的子进程的返回并修改状态
<br>语法:#include
<br>#include
<br>pid_t waitpid(pid,stat_loc,options)
<br>pid_t pid;
<br>int *stat_loc,options;
<br>说明:当pid等于-1,options等于0时,该系统调用等同于wait().否则该
<br>系统调用的行为由参数pid和options决定.
<br>pid指定了一组父进程要求知道其状态的子进程:
<br>-1:要求知道任何一个子进程的返回状态.
<br>>0:要求知道进程号为pid值的子进程的状态.
<br><-1:要求知道进程组号为pid的绝对值的子进程的状态.
<br>options参数为以比特方式表示的标志以或运算组成的位图,每个
<br>标志以字节中某个比特置1表示:
<br>WUNTRACED:报告任何未知而又已停止运行的指定进程号的子进
<br>程的状态.该子进程的状态自停止运行时起就没有被报告
<br>过.
<br>WCONTINUED:报告任何继续运行的指定进程号的子进程的状态,
<br>该子进程的状态自继续运行起就没有被报告过.
<br>WHOHANG:若调用本系统调用时,指定进程号的子进程的状态目
<br>前并不是立即有效的(即可被立即读取的),调用进程并被
<br>暂停执行.
<br>WNOWAIT:保持将其状态设置在stat_loc的进程在可等待状态.
<br>该进程将等待直到下次被要求其返回状态值.
<br>返回值:等待到一个子进程返回时,返回值为该子进程号,否则返回值为
<br>-1.同时stat_loc返回子进程的返回值.
<br>例子:pid_t pid;
<br>int stat_loc;
<br>/*父进程*/
<br>if ((pid=fork())>0) {
<br>waitpid(pid,&stat_loc,0);
<br>/*父进程等待进程号为pid的子进程的返回*/
<br>}
<br>else {
<br>/*子进程的处理过程*/
<br>exit(1);
<br>}
<br>/*父进程*/
<br>printf("stat_loc is [%d]\n",stat_loc);
<br>/*字符串"stat_loc is [1]"将被打印出来*/
<br>8.setpgrp()
<br>功能:设置进程组号和会话号.
<br>语法:#include
<br>pid_t setpgrp()
<br>说明:若调用进程不是会话首进程.将进程组号和会话号都设置为与它
<br>的进程号相等.并释放调用进程的控制终端.
<br>返回值:调用成功后,返回新的进程组号.
<br>例子:/*父进程处理*/
<br>if (fork()>0) {
<br>/*父进程处理*/
<br>}
<br>else {
<br>setpgrp();
<br>/*子进程的进程组号已修改成与它的进程号相同*/
<br>exit(0);
<br>}
<br>9.exit()
<br>功能:终止进程.
<br>语法:#include
<br>void exit(status)
<br>int status;
<br>说明:调用进程被该系统调用终止.引起附加的处理在进程被终止前全
<br>部结束.
<br>返回值:无
<br>10.signal()
<br>功能:信号管理功能
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -