在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进程的父进程输出bputchar('b');else{ if(p2=fork())           putchar('c');        //p2进程的父进程输出celse 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);elsefor(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发生后终止子进程1kill(p2, 17);              //向p1进程发17中断发生的信号wait(0);                   //阻塞父进程等待中断17发生后终止子进程2printf("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为0signal(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调用使用。它将称为一个“僵进程”。

如何避免僵尸进程

  1. 调用wait或者waitpid函数查询子进程退出状态,此方法父进程会被挂起。
  2. 如果不想让父进程挂起,可以在父进程中加入一条语句: signal(SIGCHLD,SIG_IGN);表示父进程忽略SIGCHLD信号,该信号是子进程退出的时候向父进程发送的
  3. 注册信号处理函数,在信号处理函数总调用 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

  1. wait系统调用会使父进程暂停执行,直到它的一个子进程结束为止。
  2. 返回的是子进程的PID,它通常是结束的子进程
  3. 状态信息允许父进程判定子进程的退出状态,即从子进程的main函数返回的值或子进程中exit语句的退出码。
  4. 如果status不是一个空指针,状态信息将被写入它指向的位置
  5. 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中断信号,如果接受到执行Int1signal(SIGINT,SIG_IGN);    //屏蔽CTRL+C信号pause();exit(0);}else{//进入Pid1的子进程if(pid2=fork()){//进入pid2的父进程signal(SIGUSR1,Int2);     用户自定义signal2中断信号,如果接受到执行Int2signal(SIGINT,SIG_IGN);   //屏蔽所有信号pause();exit(0);}else{//进入pid2的子进程,此时pid1=0,pid2=0signal(SIGINT,IntDelete);   //调用kill()将信号传送给当前进程所在进程组中的所有进程waitpid(-1,&exitpid,0);    //阻塞式等待printf("parent process is killed\n");  //只能输出这一个,其他的全被屏蔽了exit(0);}}
}

操作系统实验2---进程管理(更完..)相关推荐

  1. 操作系统实验二 进程管理

    进程管理 一.实验目的 1. 理解进程的概念,明确进程和程序的区别. 2. 理解并发执行的实质. 3. 掌握进程的创建.睡眠.撤销等进程控制方法. 二.实验内容与基本要求 用C语言编写程序,模拟实现创 ...

  2. 操作系统——实验二 进程管理

    1.实验目的 (1)加深对进程概念的理解,明确进程和程序的区别. (2)进一步认识并发执行的实质. (3)分析进程竞争资源现象,学习解决进程互斥的方法. 2.实验预备内容 (1)阅读Linux的sch ...

  3. 计算机操作系统实验二 进程管理

    一.实验目的 1.掌握进程的概念,明确进程的含义 2.认识并了解并发执行的实质 二.实验内容 1.编写一段程序,使用系统调用fork( )创建两个子进程.当此程序运行时,在系统中有一个父进程和两个子进 ...

  4. 操作系统 实验一 进程管理与进程同步

    理解安全性算法和银行家算法的核心机制: 针对3类资源.5个进程的情况,设计相应的数据结构,分别表示每个进程占用各类资源的情况: 编程实现安全性算法函数,编制主函数,动态输入资源的占用情况,进程的资源申 ...

  5. 操作系统实验一 Linux基本操作|实验二 进程管理

    由于当时没存代码,只有实验文档代码截图,文末也可直接获取实验文档. 操作系统实验 目录 实验一 Linux基本操作 实验二进程管理 实验一 Linux基本操作 1实验目的 1.熟悉在Linux操作系统 ...

  6. 笔记篇:操作系统第二章 进程管理

    笔记篇:操作系统第二章 进程管理 目录 笔记篇:操作系统第二章 进程管理 2.1 进程的基本概念 2.1.1 程序的顺序执行及其特征 2.1.2 前驱图 2.1.3 程序的并发执行及其特征 2.1.4 ...

  7. linux进程管理命令实验,实验2Linux进程管理.doc

    实验2Linux进程管理 实验2 Linux进程管理 实验目的 1.加深对进程概念的理解,明确进程和程序的区别 2.进一步认识并发执行的实质 3.分析进程争用资源的现象,学习解决进程互斥的方法 实验性 ...

  8. (王道408考研操作系统)第二章进程管理-第三节10:经典同步问题之哲学家进餐问题

    本文接: (王道408考研操作系统)第二章进程管理-第三节6:经典同步问题之生产者与消费者问题 ((王道408考研操作系统)第二章进程管理-第三节7:经典同步问题之多生产者与多消费者问题 (王道408 ...

  9. (王道408考研操作系统)第二章进程管理-第三节8:经典同步问题之吸烟者问题

    本文接: (王道408考研操作系统)第二章进程管理-第三节6:经典同步问题之生产者与消费者问题 ((王道408考研操作系统)第二章进程管理-第三节7:经典同步问题之多生产者与多消费者问题 文章目录 一 ...

  10. (王道408考研操作系统)第二章进程管理-第三节7:经典同步问题之多生产者与多消费者问题

    注意:生产者与消费者问题Linux系统编程专栏有案例讲解 Linux系统编程39:多线程之基于阻塞队列生产者与消费者模型 Linux系统编程40:多线程之基于环形队列的生产者与消费者模型 本文接:(王 ...

最新文章

  1. 【SharePoint】设置站点通讯组
  2. BZOJ5089 最大连续子段和(分块)
  3. map使用不存在的下标
  4. 浅析 Java Thread.join()
  5. Python 学习笔记(1)Hello Python
  6. 求两个数集的并集C++代码实现
  7. 网络流-最大流 dinic+当前弧优化(模板)
  8. mysql函数移植到oracle,oracle到mysql的迁移,函数部分
  9. java中子类与父类中隐含的this引用的分析
  10. mysql数据库事务命令_MySql学习18----数据库事务---命令使用(02)
  11. windows窗口移动到屏幕外,找回方法
  12. redis入门——集群篇
  13. 63相似、合同、相抵
  14. xshell5安装包(百度云),官方免费版,不会强制更新,没有标签限制(2019-4-4)
  15. 常用MarkDown/LaTeX语法总结
  16. 整人游戏-色盲在线测试
  17. Excel K4宏病毒专杀
  18. IBM X3650M3存储raid阵列瘫痪的恢复过程
  19. openwrt 添加usb网卡_树莓派安装OpenWrt教程
  20. 查询自己名下所有微信账户

热门文章

  1. 计算机等级对高考加分,什么是“等级赋分制”?不懂它高考要吃大亏!
  2. 短进程算法c语言,短进程优先算法C语言实现
  3. 路由器的工作原理及配置
  4. windows7下安装linux的发行版ubuntu操作系统整理
  5. WIN10 拨号连接下开启移动热点
  6. MBSE和SysML
  7. 经典算法之折半查找法
  8. python士兵突击简单小程序
  9. 1031 查验身份证 (15分)
  10. IBM热门职位随手掰掰 --- 客串猎头?