0


操作系统实验4-进程通信:(一)软中断通信

**实验时间: 2023.3.28 **

【实验目的】

UNIX/LINUX系统的进程间通信机构(IPC)允许在任意进程之间大批量地交换数据。本实验的目的是了解和熟悉Linux支持的信号量机制。

1.了解什么是信号,熟练掌握signal(),wait(),exit(),kill()函数。

2.熟悉并掌握Linux系统中进程之间采用软中断通信的基本原理。

【实验内容】

本次实验共3部分,前两部分必做。

1.编写一段程序,使用系统调用fork( )创建两个子进程,再用系统调用signal( )进行预置,让父进程捕捉由键盘发来的中断信号(即同时按下Ctrl+C键)。当系统捕捉到中断信号后,调用预置的stop函数,子进程捕捉到信号后,分别输出下列信息后终止:

Child process 1 is interrupted by parent!

Child process 2 is interrupted by parent!

父进程等待两个子进程终止后,输出以下信息后终止:

  1. Parent process is interrupted!

**<参考程序> **

  1. # include<stdio.h>
  2. # include<signal.h>
  3. # include<unistd.h>
  4. # include<sys/wait.h>
  5. int wait_mark
  6. main()
  7. {
  8. int p1, p2;
  9. signal(SIGINT,stop); //signal()初始位置
  10. while((p1=fork())==-1);
  11. if(p1>0)
  12. {
  13. while((p2=fork())= =-1);
  14. If(p2>0)
  15. {
  16. wait_mark=1;
  17. waiting( );
  18. wait(0);
  19. wait(0);
  20. printf(“parent process is interrupted!\n”);
  21. exit(0);
  22. }
  23. else
  24. {
  25. wait_mark=1;
  26. waiting( );
  27. lockf(1,1,0);
  28. printf(“child process 2 is interrupted by parent!\n”);//可以输出多条语句试试看是否能插入其他进程的输出语句
  29. lockf(1,0,0);
  30. exit(0);
  31. }
  32. }
  33. else
  34. {
  35. wait_mark=1;
  36. waiting( );
  37. lockf(1,1,0);
  38. printf(“child process 1 is interrupted by parent!\n”);//这里也可以输出多条语句试试看
  39. lockf(1,0,0);
  40. exit(0);
  41. }
  42. }
  43. void waiting()
  44. {
  45. while(wait_mark!=0);
  46. }
  47. void stop()
  48. {
  49. wait_mark=0;
  50. }

实验1要求:

(1)调试并正确运行程序,分析结果,加上注释。

(2)如果把signal(SIGINT,stop)放在①号和②号位置,结果会怎样并分析原因。

(3)该程序段前面部分用了两个wait( ),为什么?

(4)该程序段中每个进程退出时都用了语句exit(0),为什么?

2.修改并调试程序(程序中预留了几个bug),增加语句signal(SIGINT,SIG_IGN)和语句signal(SIGQUIT,SIG_IGN),再观察程序执行时屏幕上出现的现象,并分析其原因。

注意:实验前先用kill命令查询本机用户自定义信号的编号。

程序主要功能:子进程屏蔽外部中断信号(Ctrl-C);

父进程接收用户按Ctrl-C产生的外部中断信号后,

分别给两个子进程发信号(用户自定义信号)。

<参考程序>

  1. void IntSend()
  2. {
  3. kill(pid1,10);//向进程pid1发送用户自定义信号10
  4. kill(pid2,12);// 向进程pid2发送用户自定义信号12
  5. EndFlag=1;
  6. }
  7. void Print1()
  8. {
  9. printf(“child process 1 is interrupted by parent !\n”);
  10. exit(0);
  11. }
  12. void Print2()
  13. {
  14. printf(“child process 2 is interrupted by parent !\n”);
  15. exit(0);
  16. }
  17. main()
  18. {
  19. int exitcode;
  20. signal(SIGINT,SIG_IGN);
  21. signal(SIGQUIT,SIG_IGN);
  22. while((pid1=fork())==-1);
  23. if(pid1==0)
  24. {
  25. signal(SIGUSR1, Print1); //用户自定义信号1
  26. signal(SIGQUIT,SIG_IGN);
  27. pause();
  28. exit(0);
  29. }
  30. else
  31. {
  32. while((pid2=fork())= =-1);
  33. if(pid2==0)
  34. {
  35. signal(SIGUSER1, Print2); //用户自定义信号2
  36. signal(SIGQUIT,SIG_IGN);
  37. pause();
  38. exit(0);
  39. }
  40. else
  41. {
  42. signal(SIGINT, IntSend);
  43. waitpid(-1,&exitcode,0);
  44. printf(“parent process is interrupted \n”);
  45. exit(0);
  46. }
  47. }

实验2要求****:调试并正确运行程序,分析结果,加上注释。

3.司机售票员问题(选做题)

  1. 编程用fork()创建一个子进程代表售票员,司机在父进程中,再用系统调用signal()让父进程(司机)捕捉来自子进程(售票员)发出的中断信号,让子进程(售票员)捕捉来自(司机)发出的中断信号,以实现进程间的同步运行。

【实验步骤和结果】

1.列出调试通过程序的清单,并按各个程序的要求分析运行结果及增加尽可能多的注释。

2.如果做选做题,给出必要的程序设计思路和方法、或列出流程图。

3.总结上机调试过程中所遇到的问题和解决方法及感想。

实验1:

  1. 调试并正确运行程序,分析结果,加上注释。
  1. #include<stdio.h>
  2. #include<stdlib.h>//exit()函数头文件
  3. #include<signal.h>
  4. #include<unistd.h>//fork()和lockf(files,function,size)函数头文件
  5. #include<sys/wait.h>
  6. int wait_mark;//全局变量
  7. //c语言自定义函数两种位置方式
  8. void waiting()
  9. {
  10. while(wait_mark!=0);//waiting()函数当wait_mark=0后离开
  11. }
  12. void stop()
  13. {
  14. wait_mark=0;//进入stop()函数后,使wait_mark=0,然后就会使处于waiting()函数的进程离开waiting()函数。
  15. }
  16. int main()
  17. {
  18. int p1,p2;
  19. signal(SIGINT,stop); //first place:在接收到 SIGINT 信号时,执行`stop`函数。使waiting()函数等待功能失效。相当于初始化wait_mark
  20. while((p1=fork())==-1);//创建子进程进程直至成功
  21. if(p1>0) //在主进程内
  22. {
  23. while((p2=fork())==-1);//又创建一个子进程至成功
  24. if(p2>0) //在主进程中
  25. {
  26. wait_mark=1; //先设置wait_mark变量,使waiting()函数能够发挥作用
  27. waiting(); //调用waiting()函数,形成死循环
  28. wait(0); //等待子进程回收资源.调用wait,立刻阻塞自己,由wait分析当前进程中的某个子进程是否已经退出了,如果找到这样一个已经变成僵尸进程的子进程,wait会收集这个子进程的信息,并将它彻底销毁后返回;如果没有找到这样一个子进程,wait会一直阻塞直到有一个出现。
  29. wait(0); //主进程一共有两个子进程,当两个进程都完成以后再执行输出语句。一个wait对应一个子进程
  30. printf("parent process is interrupted!\n");
  31. exit(0); //正常退出,结束程序
  32. }
  33. else //在创建的第二个子进程中
  34. {
  35. wait_mark=1;
  36. waiting(); //调用waiting()函数,形成死循环
  37. lockf(1,1,0); //展开全部 lockf (1,1,0)是锁定屏幕输出,不让其他进程可以输出到屏幕,lockf (1,0,0)则是解锁.配合使用,实现进程的互斥。这里的第一个1指的是输出设备
  38. printf("child process 2 is interrupted by parent!\n");
  39. lockf(1,0,0);
  40. exit(0);
  41. }
  42. }
  43. else //在第一个创建的子进程内,重复了和子进程2 相同的步骤。
  44. {
  45. wait_mark=1;
  46. waiting(); //调用waiting()函数,形成死循环
  47. lockf(1,1,0);
  48. printf("child process 1 is interrupted by paremt!\n");
  49. lockf(1,0,0);
  50. exit(0);
  51. }
  52. }

首先,刚开始,父进程创建了两个进程,这三个进程在输入前都会进入死循环,因为子进程复制父进程,父进程对信号的监听功能也被复制。当按下ctrl+c时,父进程,p1进程,p2进程三个进程都监听到了ctrl+c,都会执行stop函数,从而把3个进程内的wait_mark置为0,跳出循环。

系统调用signal( )进行预置,让父进程捕捉由键盘发来的中断信号,当系统捕捉到中断信号后,调用预置的stop函数,子进程捕捉到信号后,输出信息终止,(child1、child2输出顺序不定。)父进程等待两个子进程结束后,也输出信息终止。

  1. 2. 如果把signal(SIGINT,stop)放在①号和②号位置,结果会怎样并分析原因。
  2. 执行signal(SIGINT,stop)调用后,进程只要接受到信号,就执行后面的stop函数。
  3. ctrlc会产生一个SIGINT的中断信号,只有父进程会捕获到这个信号,系统的默认处理是终止进程,那么其实是父进程被杀死了,子进程就会成为孤儿进程。如果不想让父进程终止,可以用signal函数来捕获SIGINT这个消息,然后自己写一个函数,当捕获到这个信号的时候,执行你的函数,进行你想要的操作。
  4. SIGINT 是由终端操作员输入中断字符 (通常为 CTRL+C) 时发出的信号。在 Linux Unix 系统中,当程序在运行时,可以通过按下 CTRL+C 来向程序发送信号,这将终止程序的运行。
  5. signal(SIGINT,stop)放在①号位置时,子进程p1就监听不到signal(SIGINT,stop)了,而ctrl+c本身就有一个中断进程的作用,这时候如果按下了ctrl+cp1直接被中断,不再等待了,也不执行子进程之后的代码了。

  1. signal(SIGINT,stop)放在②号位置时,子进程p1p2就监听不到signal(SIGINT,stop)了,而ctrl+c本身就有一个中断进程的作用,这时候如果按下了ctrl+cp1p2直接被中断,不再等待了,也不执行之后的代码了。

  1. 3. 该程序段前面部分用了两个wait( ),为什么?

父进程用了两个wait(0)的原因:父进程一共有两个子进程,等待2个子进程都结束再执行。当两个进程都完成以后再执行输出语句。

  1. 4. 该程序段中每个进程退出时都用了语句exit(0),为什么?

正常运行程序并退出程序。为了保证每个进程执行完后,shell能及时回收资源。

实验2修改并调试程序(程序中预留了几个bug),增加语句signal(SIGINT,SIG_IGN)和语句signal(SIGQUIT,SIG_IGN),再观察程序执行时屏幕上出现的现象,并分析其原因。

注意:实验前先用kill命令查询本机用户自定义信号的编号。

程序主要功能:子进程屏蔽外部中断信号(Ctrl-C);

父进程接收用户按Ctrl-C产生的外部中断信号后,

分别给两个子进程发信号(用户自定义信号)。

==不能写成= =

删去没有用的变量Endflag

  1. #include<stdio.h>
  2. #include<stdlib.h>//exit()函数头文件
  3. #include<signal.h>
  4. #include<unistd.h>//fork()函数头文件
  5. #include<sys/wait.h>
  6. int pid1,pid2,EndFlag;
  7. void IntSend()
  8. {
  9. kill(pid1,10);//向进程pid1发送用户自定义信号10
  10. kill(pid2,12);// 向进程pid2发送用户自定义信号12
  11. }
  12. void Print1()
  13. {
  14. printf(“child process 1 is interrupted by parent !\n”);
  15. exit(0);
  16. }
  17. void Print2()
  18. {
  19. printf(“child process 2 is interrupted by parent !\n”);
  20. exit(0);
  21. }
  22. int main()
  23. {
  24. int exitcode;
  25. signal(SIGINT,SIG_IGN);//忽略键信号
  26. signal(SIGQUIT,SIG_IGN);//忽略中断信号
  27. while((pid1=fork())==-1);
  28. if(pid1==0)
  29. {
  30. signal(SIGUSR1, Print1); //用户自定义信号1,使执行Print
  31. signal(SIGQUIT,SIG_IGN); //忽略中断信号。
  32. pause(); //pause()会令目前的进程暂停(进入睡眠状态), 直到被信号(signal)所中断.只有EINTR 有信号到达中断此函数的执行。
  33. exit(0);
  34. }
  35. else
  36. {
  37. while((pid2=fork())= =-1);
  38. if(pid2==0)
  39. {
  40. signal(SIGUSR2, Print2); //用户自定义信号2
  41. signal(SIGQUIT,SIG_IGN); //忽略中断信号
  42. pause();
  43. exit(0);
  44. }
  45. else
  46. {
  47. signal(SIGINT, IntSend);//接收到SIGINT信号时,执行IntSend函数。
  48. waitpid(-1,&exitcode,0);//pid=-1 等待任何子进程,此时的waitpid()函数就退化成了普通的wait()函数。exitcode保存子进程的状态信息,有了这个信息父进程就可以了解子进程为什么会退出,是正常退出还是出了什么错误。如果status不是空指针,则状态信息将被写入指向的位置。如果不关心子进程为什么推出的话,也可以传入空指针。
  49. printf(“parent process is interrupted \n”);
  50. exit(0);
  51. }
  52. }
  53. }

该程序的结果唯一确定的是: 一定有一个子进程的输出在父进程前面。由于存在异步执行,所以可能的输出顺序很难 确定。输出顺序由操作系统内核的进程调度算 法以及进程启动与退出的时间差等因素共同决 定。

  1. 当等待2s以上再按下ctrl+c,输出第一个结果。当迅速按下ctrl+c后,输出第二个结果,如图

子进程屏蔽外部中断信号,父进程接收用户按“Ctrl+C”产生的外部中断信号后,分别给两个子进程发信号(用户自定义信号)。系统调用signal( )进行预置,子进程接收自定义信号后,调用预置的print函数,打印输出。(child1、child2顺序不定)。

语句‘waitpid(-1,&exitcode,0)’代表父进程等待任何子进程,接收子进程状态结束值。等待一个子进程结束后,父进程打印输出,结束。

在父进程执行该语句之前signal(SIGINT, IntSend);,子进程处于休眠状态,直到被信号(signal)所中断。

当子进程2中 signal(SIGUSR2, Print2);被signal(SIGUSR1, Print2);替换时,会出现如下结果:

因为kill(pid2,12);// 向进程pid2发送用户自定义信号12,如果为signal(SIGUSR1, Print2);时不会接收到正确的信号。所以没有输出。

待思考

实验三司机售票员问题(选做题)

编程用fork()创建一个子进程代表售票员,司机在父进程中,再用系统调用signal()让父进程(司机)捕捉来自子进程(售票员)发出的中断信号,让子进程(售票员)捕捉来自(司机)发出的中断信号,以实现进程间的同步运行。

参考代码:

1 #include <stdio.h>

2 #include <sys/types.h>

3 #include <signal.h>

4 #include <unistd.h>

5 void saler(int); //售票员信号处理函数

6 void driver(int); //司机信号处理函数

7 pid_t pid; //保存子进程号

8 int main()

9 {

10 if ((pid = fork()) == -1)

11 {

12 perror("fork");

13 return -1;

14 }

15 if (pid == 0) //子进程(售票员)

16 {

17 signal(SIGINT, saler); //处理SIGINT信号,不退出进程(如果不注册该信号,则会本进程会退出)。注:ctrl+c可向所有进程发送退出信号,默认退出所有进程

18 signal(SIGQUIT, saler); //处理SIGQUIT信号,不退出进程。注:ctrl+\可向所有进程发送退出信号,默认退出所有进程

19 signal(SIGUSR1, saler); //处理用户自定义信号SIGUSR1

20 signal(SIGTSTP, SIG_IGN); //忽略SIGTSTP信号。注:ctrl+z可向所有进程发送该信号,暂停所有进程

21 while (1)

22 pause();

23 }

24 else //父进程(司机)

25 {

26 signal(SIGINT, SIG_IGN);

27 signal(SIGQUIT, SIG_IGN);

28 signal(SIGTSTP, driver);

29 signal(SIGUSR1, driver);

30 signal(SIGUSR2, driver);

31 while (1)

32 pause();

33 }

34 return 0;

35 }

36

37 void saler(int no)

38 {

39 if (no == SIGINT)

40 kill(getppid(), SIGUSR1); //如果接收到SIGINT信号,则向父进程(司机)发送SIGUSR1信号

41 if (no == SIGQUIT)

42 kill(getppid(), SIGUSR2);

43 if (no == SIGUSR1)

44 {

45 printf("please get off the bus!\n");

46 kill(0, SIGKILL); //杀死该进程组的所有进程

47 }

48 }

49

50 void driver(int no)

51 {

52 if (no == SIGUSR1)

53 printf("let's gogogo!\n");

54 if (no == SIGUSR2)

55 printf("stop the bus!\n");

56 if (no == SIGTSTP)

57 kill(pid, SIGUSR1); //如果接受到SIGTSTP信号,则向子进程(售票员)发送SIGUSR1信号

58 }

参考:

进程通信——司机售票员问题_weixin_30483495的博客-CSDN博客

操作系统 实验四 进程间通信实验_进程间的通信实验_软件源码的博客-CSDN博客

【实验总结和体会】

  1. 通过调用 signal(SIGINT, stop) 函数,程序可以在接收到 SIGINT 信号时,执行 stop 函数。这在需要在紧急情况下或程序出现异常时快速停止程序的情况下非常有用。

    2.signal()函数部分用法和总结

void(* signal(int sig,void(* func)(int)))(int);

设置处理信号的功能:

指定使用sig指定的信号编号处理信号的方法。参数func指定程序可以处理信号的三种方式之一:

默认处理(SIG_DFL):信号由该特定信号的默认动作处理。

忽略信号(SIG_IGN):忽略信号,即使没有意义,代码执行仍将继续。

函数处理程序:定义一个特定的函数来处理信号。

signal(SIGINT,SIG_IGN)和signal(SIGQUIT,SIG_IGN)分别为忽略键信号(即中断,当用户从键盘按^c键或^break键)以及忽略中断信号。SIGQUIT和SIGINT类似, 但由QUIT字符(通常是Ctrl+)来控制. 进程在因收到SIGQUIT退出时会产生core文件, 在这个意义上类似于一个程序错误信号。

linux——signal信号(SIGHUP、SIGINT、SIGQUIT、SIGILL、SIGTRAP、SIGABRT..)-CSDN博客

  1. ​​​​​​​3. 头文件

<unistd.h> 这个头文件中包含了一些与操作系统交互的函数,比如文件操作、进程管理、系统调用等。具体包含的函数可以查看该头文件的文档或者man手册。常用的一些函数有:

fork():创建一个新的进程。

exec():启动一个新的进程来执行一个指定的可执行文件。

getpid():获取当前进程的PID。

getcwd():获取当前工作目录。

chdir():改变当前工作目录。

sleep():使当前进程休眠指定的时间。

pause(void)

lockf(files,function,size),

  1. <stdlib.h> 这个头文件中包含了一些通用的函数,比如内存管理、随机数生成等。常用的一些函数有:

malloc():分配指定大小的内存空间。

calloc():分配指定数量的指定大小的内存空间。

realloc():重新分配指定大小的内存空间。

free():释放之前分配的内存空间。

rand():生成一个随机数。

atoi():将字符串转换成整数。

exit():退出程序。

  1. <stdio.h>

提供了一些用于输入/输出操作的函数,

例如printf()、scanf()、fopen()、fclose()等。

需要注意的是,这些函数都是C语言标准库提供的函数,不是Linux特有的。不过在Linux中,这些函数都是通过系统调用实现的。

linux常用的C语言头文件#include <unistd.h> #include <stdlib.h> #include <stdlib.h>的作用-CSDN博客

  1. 4.

Linux中waitpid()函数的用法-CSDN博客

  1. 5.系统调用 lockf files,function,size), 用作锁定文件的某些段或者整个文件。本函数的头文件为 include<unistd.h> 其中 file 为文件描述符,function 是锁定和解锁:1 表示锁定,0 表示解锁; size 是锁定或者解锁的字节数,为 0,表示从文件的当前位置到文件尾。
  2. 6. **软中断通信:**为了满足实时系统的要求,中断处理应该是越快越好。linux为了实现这个特点,当中断发生的时候,硬中断处理那些短时间就可以完成的工作,而将那些处理事件比较长的工作,放到中断之后来完成,也就是软中断来完成。基本的思路是: 父进程生成子进程,父进程发送信号并等待,子进程接收信号,然后自我终止并唤醒父进程,父进程再自我终止。
  3. 7.终端中弹出用户名的控制,通常是由操作系统的Shell控制的。Shell是操作系统中的一个命令行解释 器,它接收用户输入的命令,并将其翻译为操作系统能够执行的指令。当用户在终端中打开Shell时,Shell会提示用户输入用户名和密码,用于进行身份验证和权限管理。一旦身份验证成功,Shell会在终 端中显示当前登录用户的用户名,并等待用户输入命令。
  4. 或者 时间片轮转依次结束
标签: linux unix

本文转载自: https://blog.csdn.net/mrqzgmzlb/article/details/129899843
版权归原作者 mrqzgmzlb 所有, 如有侵权,请联系我们删除。

“操作系统实验4-进程通信:(一)软中断通信”的评论:

还没有评论