当前位置:   article > 正文

linux 操作系统实验(六)_2、使用系统调用 fork()函数创建两个子进程,再用系统调用 signal()函数让父进程接

2、使用系统调用 fork()函数创建两个子进程,再用系统调用 signal()函数让父进程接
  1. 掌握进程间通信之信号的用法
  2. 掌握进程间通信之共享内存的使用方法
  3. 掌握进程间通信之消息队列使用。
  1. 信号实验;参考下以关于信号注册和信号捕捉的函数代码/*signal.c*/
  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include<signal.h>
  4. void f(int sig)
  5. {
  6. printf("catch signal %d\n",sig);
  7. }
  8. int main()
  9. {
  10.               int i=0;
  11.               signal(SIGINT,f);
  12.               printf("pid=%d\n",getpid());
  13.               while(1)
  14.               {
  15.                    printf("%d\n",i++);
  16.                    sleep(1);
  17.               }
  18.               return 0;
  19. }

要求:

1)将上题中的signal()函数改为sigaction()函数。

2)编写一段程序,使用系统调用函数fork( )创建两个子进程,再用系统调用函数

signal( )让父进程捕捉信号SIGINT(可以键入ctrl+c 来触发,也可以用kill 命令来

触发),当捕捉到中断信号后,父进程用系统调用函数kill( )向两个子进程发出信

号,子进程捕捉到父进程发来的信号后,分别输出下列信息后终止:

Child process 1 is killed by parent!

Child process 2 is killed by parent!

 

2.

                             

TIPS:

1.pause()函数是阻塞型的,并且非常重要的一点就是pause是不分信号类型的,只要能够捕捉到任何信号,它就会正确返回。它经常用来判断信号到底有没有到达,因为没有到达就会阻塞。

2.kill()函数是属于一个系统接口,它的用法是用来发送信号,而不是直接就kill掉一个进程。

3.linux系统中专门会建立一个信号队列(实时信号),用来处理接收到的各种信号,但注意,(非实时信号)同一种信号只会处理一次,这个队列中每个格子对应一种信号,同种信号如果有非常多个,也只会处理一次。而实时信号则不会造成信号丢失。

                  参考博客:https://blog.csdn.net/c1194758555/article/details/52848114

                  解析信号机制的一篇非常好的博客:https://blog.csdn.net/return_cc/article/details/78845346

基础工作流程:

1.用户可以使用alarm定时器,定时从内核发起信号发送给用户进程(或者使用kill发送信号给某进程)。它的流程大概是这样:

                                              

2.接下来是信号的收集以及后续的处理,signal(或者sigaction)函数,用于对指定的信号做指定的处理。如果不对信号处理,那么信号会被操作系统默认处理。

以上两个步骤只是针对32个标准信号的处理。

如果要处理一些特定的用户非常感性趣的信号,一般常用信号集合函数处理

1.创建信号集合,这个集合会专门编写一个处理函数去处理,这个处理函数就是sigaction。

2.信号集合创建完成后并不能马上就去使用信号处理函数,这个集合里可能含有不需要/不希望被处理的函数,因此使用信号屏蔽字sigprocmask去指定集合里面哪些信号被阻塞(信号一旦被阻塞就不能被进程接收到),起到过滤作用

3.使用信号处理函数sigaction处理特定希望处理的信号

4.还可以让进程检测,处理那些被屏蔽的信号,因为被屏蔽后,进程不知道还有哪些信号是存在的。

PS:所谓的屏蔽信号就是阻塞信号。

 

 

对于做实验一第二小题,我搞了大概6天,是的,一个实验搞了6天。终于弄通了。上面的例子只是科普下信号的基本知识,但是对于信号执行的机制还不是很懂,下面我用我的实验程序来讲解:

首先的我实验结论是:

1.子进程是以什么样的结果结束的,就必然会返回一个结果给父进程,当然这个结果可以是一个信号值,也可以是一个代表正常结束的0值,如果由于信号原因终止,父进程也会收到信号,即子进程退出时会给父进程发信号。

2.非可靠信号不排队,且执行信号处理程序时,会自动BLOCK当前处理的信号。这句话出自APUE这本书。意思就是,信号处理函数在捕捉到信号时,会执行信号 处理函数,并且,该种信号会被阻塞。这个结论,等下程序会证明。

3.kill不允许自身信号的发送

4.通过下面的宏来判断子进程的状态,尽管实际上编程时是比较少用的:

常用宏函数分为日如下几组: 
(1)、 WIFEXITED(status) 若此值为非0 表明进程正常结束。 
若上宏为真,此时可通过WEXITSTATUS(status)获取进程退出状态(exit时参数) 

(2)、 WIFSIGNALED(status)为非0 表明进程异常终止。 
若上宏为真,此时可通过WTERMSIG(status)获取使得进程退出的信号编号 

(3)、 WIFSTOPPED(status)为非0 表明进程处于暂停状态 
若上宏为真,此时可通过WSTOPSIG(status)获取使得进程暂停的信号编号 
(4)、 WIFCONTINUED(status) 非0表示暂停后已经继续运行。

(5) sleep函数虽然会使得进程暂时进入睡眠,但是一旦收到信号,进程马上会取消sleep态而进入响应,所以如果进程仍然是sleep的说明没有收到信号。

以上宏说明来自博客      :https://blog.csdn.net/y396397735/article/details/53769865

实验如图:

实验结果:

结果分析:

请看实验程序和实验结果的箭头,当循环到1的时候,才设置了signal函数。所以,从一开始,由于kill发送给子进程信号中断,同时父进程没有设置signal函数,所以,i的值为0,1时,signal函数是不生效的,实验也看到,子进程也是中断才退出的。到了2以后,signal生效了,但是子进程确是正常退出了,首先父进程是没有办法收到中断信号的,除非是子进程退出会发送,父进程就能接收到。那么在i的值为1 的时候,子进程退出后,就发了中断信号给父进程了,那么由于信号处理函数会阻塞当前信号,下一次创建的子进程就收不到信号,中断信号停在了父进程这里(此时父进程还是活的,信号是被保留的,只不过阻塞了打不了给子进程)。即使你使用kill函数发送,也是失效的。

为了严谨些,注释掉一些代码:

上述实验代码把这个signal注释掉看看:

可以看到,子进程全部都是由于中断信号退出,非常棒!(子进程sleep后面的语句不执行,因为被中断了。)

 

2.练习sigprocmask函数的用法,程序填空。

实验结果:

可以看到,一旦设置屏蔽,进程将收不到这个中断,直到解除信号阻塞。

 

 

3、用共享内存实现进程间通讯,实现 reader 和 writer 两个进程分别为rosse.c 和jack.c,使它们通过共享内存交换数据。writer 从标准输入读入字符串写入共享内存,reader 把共享内存里的字符串打印到标准输出。

reader 和 writer 通过信号量实现同步

 

要做共享内存的实验,需要创建一片共享内存,顺便提一下,在操作系统中,提及的共享内存,一般以段表的形式实现,那么共享内存就是内核的一片虚拟内存,等到各个进程需要访问时,再进行映射到各个进程真实地址,言归正传,共享内存的创建需要IPC键值,这个东西是可以用ftok来创建,注意,这个ftok是利用同一个文件系统内inode值的唯一性加上一个子序列,创建出IPC键值的,这个键值就是个共享内存块标志,没有别的意义(你可以这样想,inode是磁盘的东西,某个文件标志啥的跟你内存有啥关系)。

shmget函数是创建一片虚拟共享内存,返回值是一个flag,标识着这片空间。

shmat函数是将创建的共享内存进行映射,返回值是具体的各个进程的内存地址。

 

实验如图:

rose.c:

结果:

这就是共享内存实验。

上述是属于linux的system-V信号量

还有POSIX有名信号量和无名信号量:

附上有名信号量版本的rose.c:

 

4.参考课本消息队列的实例,分别编写代表两个人的程序,他们之间用消息队列进行通信。消息队列中的消息类型是两类,分别是J2R代表jack发给rose的消息,R2J 代表Rose发给Jack的消息。

                                                  

写端代码:

  1. #include <sys/types.h>
  2. #include <sys/ipc.h>
  3. #include <sys/msg.h>
  4. #include <stdio.h>
  5. #include <string.h>
  6. struct message
  7. {
  8. long type;
  9. char text[80];
  10. };
  11. int main()
  12. {
  13. key_t key=ftok(".",123);
  14. int msgid=msgget(key,IPC_CREAT|0666);
  15. struct message a;
  16. bzero(&a,sizeof a);
  17. fgets(a.text,80,stdin);
  18. a.type=200;
  19. msgsnd(msgid,&a,strlen(a.text),0);
  20. }

 

读端:

  1. #include <sys/types.h>
  2. #include <sys/ipc.h>
  3. #include <sys/msg.h>
  4. #include <stdio.h>
  5. #include <string.h>
  6. struct message
  7. {
  8. long type;
  9. char text[80];
  10. };
  11. int main()
  12. {
  13. key_t key=ftok(".",123);
  14. int msgid=msgget(key,IPC_CREAT|0666);
  15. struct message a;
  16. bzero(&a,sizeof a);
  17. //a.type=200;
  18. msgrcv(msgid,&a,80,200,0);
  19. printf("%s",a.text);
  20. }

 

消息队列是存在于系统内核维护的一种数据结构,使用时会在内核中保存,需要撤销时,用msgctl删除。

关于消息队列的使用,参考博客:https://www.cnblogs.com/linjiqin/p/5720865.html

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/210464
推荐阅读
相关标签
  

闽ICP备14008679号