在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID(>0)。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。
1.进程的创建
编写一段程序,使用系统调用fork( )创建两个子进程。当此程序运行时,在系统中有一个父进程和两个子进程活动。让每一个进程在屏幕上显示一个字符;子进程显示字符“a”,父进程分别显示字符“b”和“c”。试观察记录屏幕上的显示结果,并分析原因。#include<stdio.h> #include<unistd.h> int main() { int p1,p2; if(p1=fork()) //p1进程的父进程输出b putchar('b'); else { if(p2=fork()) putchar('c'); //p2进程的父进程输出c else putchar('a'); //p2进程的子进程输出a } }
p1进程fork之后,p1变为父进程,生成一个p1的子进程,此时作为父进程的p1返回的不是0,好,输出b,此时作为p1的子进程p儿子1由于是返回的0所以进入else判断区域,此时遇到了p2进程,同样上面操作,在p儿子1手下{p2作为了父亲输出了c,p2儿子输出了a}但是是由于两个进程就是独立个体,各自运行,互不干扰,父子进程谁先执行不由fork决定,而是由系统当前环境和进程调度算法决定,因此根据个人系统情况不同,打出上述所有的顺序都不一定。
2.进程的控制 <任务> 修改已编写好的程序,将每个程序的输出由单个字符改为一句话,再观察程序执行时屏幕上出现的现象,并分析其原因。如果在程序中使用系统调用lockf()来给每个程序加锁,可以实现进程之间的互斥,观察并分析出现的现象。
#include<stdio.h> #include<unistd.h> int main() { int p1,p2,i; if(p1=fork()) for(i=0;i<500;i++) printf("child %d\n",i); else { if(p2=fork()) for(i=0;i<500;i++) printf("son %d\n",i); else for(i=0;i<500;i++) printf("daughter %d\n",i); } }
输出的过程是不会被打断的,能完整输出一条语句,但是每条语句之间的顺序还是不确定的
child…. son… daughter… daughter… 或child …son …child …son …daughter 分析:由于函数printf()输出的字符串之间不会被中断,因此,字符串内部的字符顺序输出时不变。但是 , 由于进程并发执行时的调度顺序和父子进程的抢占处理机问题,输出字符串的顺序和先后随着执行的不同而发生变化。这与打印单字符的结果相同。
#include<stdio.h> #include<unistd.h> int main() { int p1,p2,i; if(p1=fork()) { lockf(1,1,0); for(i=0;i<500;i++) printf("child %d\n",i); lockf(1,0,0); } else { if(p2=fork()) { lockf(1,1,0); for(i=0;i<500;i++) printf("son %d\n",i); lockf(1,0,0); } else { lockf(1,1,0); for(i=0;i<500;i++) printf("daughter %d\n",i); lockf(1,0,0); } } }
lockf(1,1,0)是锁定屏幕输出,不让其他进程可以输出到屏幕,lockf(1,0,0)则是解锁,使得每一个for循环都能完整执行并输出,但是三个输出的顺序依然不定。大致与未上锁的输出结果相同,也是随着执行时间不同,输出结果的顺序有所不同。 分析:因为上述程序执行时,不同进程之间不存在共享临界资源(其中打印机的互斥性已有由操作系统保证)问题,所以,加锁与不加锁效果相同。
3.软中断通信 〈任务1〉 编制一段程序,使用系统调用fork()创建两个子进程,再用系统调用signal()让父进程捕捉键盘上来的中断信号(即按ctrl+c键),当捕捉到中断信号后,父进程用系统调用kill()向两个子进程发出信号,子进程捕捉到信号后,分别输出下列信息后终止: child process1 is killed by parent! child process2 is killed by parent! 父进程等待两个子进程终止后,输出以下信息后终止: parent process is killed!
<程序流程图>
SIGINT信号:程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl-C)时发出,用于通知前台进程组终止进程。接收默认处理:接收默认处理的进程通常会导致进程本身消亡。例如连接到终端的进程,用户按下CTRL+c,将导致内核向进程发送一个SIGINT的信号,进程如果不对该信号做特殊的处理,系统将采用默认的方式处理该信号,即终止进程的执行。
#include<stdio.h> #include<signal.h> #include<unistd.h> #include<stdlib.h> #include<wait.h> void waiting(), stop(),alarming(); int wait_mark; int main() { int p1, p2; if (p1 = fork()) /*创建子进程p1*/ {//子进程1创建成功,当前运行的是子进程1的父进程 if (p2 = fork()) /*创建子进程p2*/ {//子进程2创建成功,当前运行的是子进程2的父进程 wait_mark = 1; signal(SIGINT, stop); //不到5s就捕获CTRL+C信号,执行stop操作 signal(SIGALRM,alarming); //接收到SIGALRM信号,执行alarming操作 waiting(); //如果捕捉到此时wait_mark=0不等待 kill(p1, 16); //向p1进程发16中断发生的信号 wait(0); //阻塞父进程等待中断16发生后终止子进程1 kill(p2, 17); //向p1进程发17中断发生的信号 wait(0); //阻塞父进程等待中断17发生后终止子进程2 printf("parent process is killed!\n"); exit(0); //终止父进程 } else {//当前是在子进程2创建成功的条件下运行的是子进程的2的子进程 wait_mark = 1; signal(17,stop); signal(SIGINT,SIG_IGN); while(wait_mark!=0); lockf(1, 1, 0); printf("child process2 is killed by parent!\n"); lockf(1, 0, 0); exit(0); } } else {//当前是在子进程1创建成功的条件下运行的是子进程1的子进程 wait_mark = 1; signal(16,stop); //接收到中断16发生的信号,置wait_mark为0 signal(SIGINT,SIG_IGN); //CTRL+C信号发生时,都会接收到信号,会把子进程给结束,所以要屏蔽掉。 while(wait_mark!=0); //wait_mark不为0会锁死在这,这就是5s内都不干程序也什么都不干的原因 lockf(1, 1, 0); printf("child process1 is killed by parent!\n"); lockf(1, 0, 0); exit(0); } } void waiting() { sleep(5); //5s后唤醒 if(wait_mark!=0) kill(getpid(),SIGALRM); //如果5s之后检测wait_mark不是0,发出SIGALRM信号 } void alarming() { wait_mark=0; } void stop() { wait_mark = 0; }
〈任务2〉 在上面的任务1中,增加语句signal(SIGINT,SIG_IGN)和语句signal(SIGQUIT,SIG_IGN),观察执行结果,并分析原因。这里,signal(SIGINT,SIG_IGN)和signal(SIGQUIT,SIG_IGN)分别为忽略键信号以及忽略中断信号。
int kill(pid_t pid, intsignum); 功能:给指定进程发送信号。
注意:使用 kill() 函数发送信号,接收信号进程和发送信号进程的所有者必须相同,或者发送信号进程的所有者是超级用户。
参数:pid: 取值有 4 种情况 :pid > 0: 将信号传送给进程 ID 为 pid 的进程。
pid = 0: 将信号传送给当前进程所在进程组中的所有进程。
pid = -1: 将信号传送给系统内所有的进程。
pid < -1: 将信号传给指定进程组的所有进程。这个进程组号等于 pid 的绝对值。
signum: 信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令 kill -l ("l" 为字母 ) 进行相应查看。
返回值:
成功:0
失败:-1
waitpid系统调用在Linux函数库中的原型是:
#include <sys/types.h> /* 提供类型pid_t的定义 */
#include <sys/wait.h>
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的绝对值。status如果不为空,会把状态信息写到它指向的位置,返回值:成功返回等待子进程的pid,失败返回-1
options:options提供了一些额外的选项来控制waitpid,目前在Linux中只支持WNOHANG和WUNTRACED两个选项,这是两个常数,可以用"|"运算符把它们连接起来使用,比如:
ret = waitpid(-1, NULL, WNOHANG | WUNTRACED);
如果我们不想使用它们,也可以把options设为0,如:
ret = waitpid(-1, NULL, 0);
如果使用了WNOHANG参数调用waitpid,即使没有子进程退出,它也会立即返回,不会像wait那样永远等下去。
僵尸进程
当一个子进程先于父进程结束运行时,它与其父进程之间的关联还会保持到父进程也正常地结束运行,或者父进程调用了wait才告终止。
子进程退出时,内核将子进程置为僵尸状态,这个进程称为僵尸进程,它只保留最小的一些内核数据结构,以便父进程查询子进程的退出状态。
进程表中代表子进程的数据项是不会立刻释放的,虽然不再活跃了,可子进程还停留在系统里,因为它的退出码还需要保存起来以备父进程中后续的wait调用使用。它将称为一个“僵进程”。
如何避免僵尸进程
- 调用wait或者waitpid函数查询子进程退出状态,此方法父进程会被挂起。
- 如果不想让父进程挂起,可以在父进程中加入一条语句: signal(SIGCHLD,SIG_IGN);表示父进程忽略SIGCHLD信号,该信号是子进程退出的时候向父进程发送的
- 注册信号处理函数,在信号处理函数总调用
wait
函数。SIGCHLD 信号
当子进程退出的时候,内核会向父进程发送SIGCHLD信号,子进程的退出是个异步事件(子进程可以在父进程运行的任何时刻终止)
如果不想让子进程编程僵尸进程可在父进程中加入:
signal(SIGCHLD,SIG_IGN)
;如果将此信号的处理方式设为忽略,可让内核把僵尸子进程转交给init进程去处理,省去了大量僵尸进程占用系统资源。(父进程忽略了
SIGCHLD
信号之后,会将僵尸子进程转交给init
进程给处理,这样子就不保存父子关系了吗?)。wait() 函数
#include <sys/types.h> #include <sys/wait.h> pid_t wait(int *status);
进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。
参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。但如果我们对这个子进程是如何死掉的毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可以设定这个参数为NULL,就象下面这样:
`pid = wait(NULL)`
如果成功,wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHILD
- wait系统调用会使父进程暂停执行,直到它的一个子进程结束为止。
- 返回的是子进程的PID,它通常是结束的子进程
- 状态信息允许父进程判定子进程的退出状态,即从子进程的main函数返回的值或子进程中exit语句的退出码。
- 如果status不是一个空指针,状态信息将被写入它指向的位置
waitpid() 函数
waitpid()
是一个非常有用的函数,不单单可以等待子进程。 还可以等待进程组中的任意一个进程。 可以看到wait()
函数实际上是waitpid()
函数的特例。#include <sys/types.h> #include <sys/wait.h> pid_t waitpid(pid_t pid, int *status, int options); 参数: status:如果不是空,会把状态信息写到它指向的位置,与wait一样 options:允许改变waitpid的行为,最有用的一个选项是WNOHANG,它的作用是防止waitpid把调用者的执行挂起 (也就是不阻塞父进程)
对于waitpid的p i d参数的解释与其值有关:
pid == -1 等待任一子进程。于是在这一功能方面waitpid与wait等效。pid > 0 等待其进程I D与p i d相等的子进程。
pid == 0 等待其组I D等于调用进程的组I D的任一子进程。换句话说是与调用者进程同在一个组的进程。
pid < -1 等待其组I D等于p i d的绝对值的任一子进程
wait与waitpid区别:
在一个子进程终止前, wait 使其调用者阻塞,而waitpid 有一选择项,可使调用者不阻塞。
waitpid并不等待第一个终止的子进程—它有若干个选择项,可以控制它所等待的特定进程。
实际上wait函数是waitpid函数的一个特例。waitpid(-1, &status, 0);原文链接:
https://blog.csdn.net/wyhh_0101/article/details/83933308
必看!!!!绝了:https://www.cnblogs.com/Anker/p/3271773.html
https://www.cnblogs.com/lsgxeva/p/8051446.html
#include<stdio.h> #include<signal.h> #include<unistd.h> #include<stdlib.h> #include<wait.h> int pid1,pid2; int EndFlag=0; int pf1=0; int pf2=0; void IntDelete() { kill(pid1,16); kill(pid2,17); EndFlag=1; } void Int1() { printf("child process 1 is killed !by parent\n"); exit(0); } void Int2() { printf("child process 2 is killed !by parent\n"); exit(0); } int main() { int exitpid; signal(SIGINT,SIG_IGN); //对产生的SIGINT信号进行屏蔽,按下CTRL+C没反应 signal(SIGQUIT,SIG_IGN); //对中断信号进行屏蔽 if(pid1=fork()) {//进入pid1的父进程 signal(SIGUSR1,Int1); //用户自定义signal1中断信号,如果接受到执行Int1 signal(SIGINT,SIG_IGN); //屏蔽CTRL+C信号 pause(); exit(0); } else {//进入Pid1的子进程 if(pid2=fork()) {//进入pid2的父进程 signal(SIGUSR1,Int2); 用户自定义signal2中断信号,如果接受到执行Int2 signal(SIGINT,SIG_IGN); //屏蔽所有信号 pause(); exit(0); } else {//进入pid2的子进程,此时pid1=0,pid2=0 signal(SIGINT,IntDelete); //调用kill()将信号传送给当前进程所在进程组中的所有进程 waitpid(-1,&exitpid,0); //阻塞式等待 printf("parent process is killed\n"); //只能输出这一个,其他的全被屏蔽了 exit(0); } } }
操作系统实验2---进程管理(更完..)
最新推荐文章于 2022-05-20 08:50:17 发布