进程间通信-信号-pipe-fifo

一、编译运行附件中的代码,提交运行结果截图

UNIX进程间通信 (IPC) 方式包括管道、FIFO以及信号。
这些部分大伙尽量别抄……很多内容比较陌生,重在理解,自己去找找收获会很多滴

(一)信号

一、基本概念理解

我们在Linux的shell中可以通过kill-l或man 7 signal命令来查看信号的信息。

*kill -l

每个信号都有一个编号和一个宏定义名称

*man 7 signal

通过man 7 signal我们可以了解到这些信号各自在什么条件下产生,默认的处理动作是什么

*stty -a

查看终端中哪些按键组合可以产生信号

intr = ^C; quit = ^; erase = ^?; kill = ^U; eof = ^D; eol = ;
eol2 = ; swtch = ; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R;
werase = ^W; lnext = ^V; discard = ^O; min = 1; time = 0;

二、编译运行代码

1.sigdemo1.c

编译运行程序

程序分析

这个程序捕捉了中断信号SIGINT,当用户在终端中按CTRL+C产生中断信号SIGINT时,函数就被调用。

第16行演示了如何定义信号处理函数。信号处理函数名可以任意起,但最好能起个有意义的名字。函数的参数必须是int类型,返回值必须是void类型。

2.sigdemo2.c

编译运行程序

程序分析

alarm超时产生SIGALRM信号,这个程序实际就是用 alarm 和 pause 实现 sleep(3)函数。
这个程序通过把信号处理函数指针传为SIGIGN,来达到忽略中断信号SIGINT的作用。

3.sigdemo3.c

编译运行程序并分析

这个程序处理两个信号: 终端信号和退出信号。其中在信号处理函数中调用了sleep函数进行休眠了几秒

  • 正常演示

  • 多信号处理-打断的情况

在Linux下,在一个信号没处理完的情况下,来了不同的信号,会优先处理后来的信号

4.sigactdemo.c

编译运行程序

程序分析

从上面的实验我们知道Signal函数存在一些问题:

  • 不知道信号被发送的原因
  • 信号处理过程中不能安全地阻塞其他信号
    要解决这个问题我们可以用sigaction来处理信号

可见,我们接收了SIGINT这个信号量以后,让程序休眠了2*4秒,在这期间我不断地向程序传入SIGQUIT信号量,但是因为我们使用sigaction来让我们的程序在信号处理过程中安全地阻塞了SIGQUIT这个信号量,所以可以看到程序先提示done handling signal 2,最后才执行SIGQUIT这个信号量的Quit

5.sigactdemo2.c

编译运行程序

程序分析

alarm超时产生SIGALRM信号,这个程序实际就是用 alarm 和 pause 实现 sleep(3)函数。

  1. main 函数调用 mysleep 函数,后者调用 sigaction 注册了 SIGALRM 信号的处理函数 sig_alrm。
  2. 调用 alarm(nsecs)设定闹钟。
  3. 调用 pause 等待,内核切换到别的进程运行。
  4. nsecs 秒之后,闹钟超时,内核发 SIGALRM 给这个进程。
  5. 从内核态返回这个进程的用户态之前处理未决信号,发现有 SIGALRM 信号,其处理函数是 sig_alrm。
  6. 切换到用户态执行 sig_alrm 函数,进入 sig_alrm 函数时 SIGALRM 信号被自动屏蔽,从 sig_alrm 函数返回时 SIGALRM 信号自动解除屏蔽。然后自动执行系统调用 sigreturn 再次进入内核,再返回用户态继续执行进程的主控制流程(main 函数调用的 mysleep 函数)。
  7. pause 函数返回-1,然后调用 alarm(0)取消闹钟,调用 sigaction 恢复SIGALRM 信号以前的处理动作。

(二)无名管道PIPE

一、基本概念理解

Linux中,对系统进行操作时,就需要I/O设备与系统产生交互,同时会产生三种数据,标准输入(0)、标准输出(1)、标准错误(2)。其中标准输入(stdin)默认接受来自键盘的输入,标准输出(stdout)和标准错误(stderr)默认向终端窗口输出,改变默认输出和出入的位置,就是I/O重定向。

二、编译运行代码

1.testttf.c

编译运行程序

程序分析

write(int handle,void *buf,int nbyte); 第一个参数是文件描述符,第二个参数是指向一端内存单元的指针,第三个参数是要写入指定文件的字节个数;成功时返回字节个数,否则返回-1。
默认的连接就是tty。在unix和linux中,shell默认链接着stdin,stdout,stderr三个文件。很多程序没有输入,只是把正确的输出写到文件描述符位1,并且把错误消息写到文件描述符2中。如果输入sort,按回车键,终端将会连接到sort工具上。随便输入几行文字,当按下Ctrl-D来结束文字输入的时候,sort程序对输入进行排序并将结果写到stdout.

2.listargs.c

编译运行程序

程序分析

这个其实没啥好讲的,只是把标准输入tty写到文件描述符2标准错误(stderr),然后还是默认向终端窗口输出罢了。

3.stdinredir1.c

接下来演示如何将stdin定向到文件,即Linux中三种将标准输入定位到文件的方式。
在这里,stdinredir1.c使用close-then-open策略。
具体学习请参见:https://blog.csdn.net/weixin_42508114/article/details/116552603

编译运行程序

程序分析

然后关闭fd0,打开一个磁盘文件,然后从标准输入再读取三行,从而实现将文件描述符0替换为到文件的连接来重定向标准输入。
开始的时候,系统采用经典的设置,从标准输入读取三行,即将三种标准流连接到终端上。然后关闭fd0,使用close(0),即将标准输入流关闭。最后,使用open(filename,mode)打开一个想连接到【stdin】的文件,(这里解释下,为什么打开i的文件会自动定向【stdin】,因为Unix中产生新的文件描述符使用‘最低可用文件描述符’的原则,当前最低的是0号位,即【stdin】)。这里我们选择O_RDONLY模式打开/etc/passwd",连接到stdin。

4.stdinredir2.c

接下来也是演示如何将stdin定向到文件,即Linux中剩下两种种将标准输入定位到文件的方式。
同样,具体学习请参见:https://blog.csdn.net/weixin_42508114/article/details/116552603

编译运行程序
第一种:使用【open】-【close】-【dup】-【close】策略

程序分析:
Unix系统调用dup建立指向已经存在的文件描述符的第二个连接。这种方法需要4个步骤。

  • open(file):第一步是打开【stdin】将要重定向的文件。这个调用返回一个文件描述符,这个描述符并不是0,而是最低可用文件描述符。
  • close(0):下一步是将文件描述符0关闭。
  • dup(fd):系统调用dup(fd)将文件描述符fd复制一个。此次复制使用最低可用文件描述符号。因此,获得的文件描述符是0.
  • close(fd):最后关闭fd,只留下文件描述符0与文件连接。
第二种:使用【open】-【dup2】-【close】策略

程序分析:
程序srdinredir2.c包含了条件编译#ifdef,系统调用dup2(fd,0)来替换close(0)和dup(fd)。

5.whotofile.c

为其他程序重定向I/O:who > userlist

编译运行程序

程序分析

//show how to redirect output for another program idea: fork, then in the child, redirect output, then exec
文件描述符集合通过exec调用传递且不会被改变,shell使用进程通过fork产生子进程与子进程调用exec之间的时间间隔来重定向标准输入、输出到文件。

6.pipedemo.c

管道编程:管道自己给自己发送数据

编译运行程序

程序分析

这个程序创建了一个管道,写入写入端,然后运行并从读取端读取。虽然有点奇怪,但证明了如何创建和使用管道。

7.pipedemo2.c

管道编程:使用fork来共享信道

编译运行程序

程序分析

父亲继续写入和读取管道,但孩子也一直在写入管道,验证共享信道。

8.pipe.c

演示如何创建从一个流程到另一个流程的管道

编译运行程序

程序分析

取两个参数,每个参数一个命令,并将av[1]的输出连接到av[2]的输入。用法和命令1 |命令2差不多,但是有些限制:由于已知的参数数,命令不接受使用execlp()的参数。

9.watch.sh和watch2.sh

它们的功能都是当其他用户登录系统或者注销时发出通知
watch2.sh更全面一点,下面给出了详细的解释

#!/bin/sh
#
# watch.sh - a simple version of the watch utility, written in sh
#
    who | sort > prev        # 第一次查看,结果放到prev文件中
    while true            
    do
        sleep 30        # 等一会儿
        who | sort > current    # 再次查看,结果放到current文件中
        echo "Logged out:"    
        comm -23 prev current    # -23表示不显示第二个文件独有的,也不显示两个文件共有的,所以这里只显                                 #示第一个文件prev中有的用户,也就是说明这些用户当前已经不在线了,即lo                                 #g out了
        echo "Logged in:"   
        comm -13 prev current    # -13表示不显示第一个文件独有的,也不显示两个文件共有的,所以这里只                                   #显示第二个文件current中有的用户,也就是说明这些用户是新来的,即log                                  #on了

        mv current prev          # 把当前文件current命名为prev,下次循环时,这个prev就表示之前存在的                                  # 用户了。

(三)有名管道FIFO

一、基本概念理解

Linux命名管道非常适合同一机器上两个进程之间传递数据,其形式也是一个文件,但是读取与写入时遵循FIFO的原则。在使用命名管道时有两种方式进行读/写:阻塞与非阻塞。使用命名管道很容易写出生产者与消费者的程序。

二、编译运行代码

1.testmf.c

编译运行程序

程序分析

管道文件的创建可以直接在shell中使用mkfifo命令:mkfifo filename
也可以使用mkfifo 函数 (在代码中使用其创建管道文件)创建FIFO:int mkfifo(const char *filename, mode_t mode);
在这里,我们可以看到我们成功地创建了管道文件myfifo

2.producer.c与consumer.c

编译运行程序

程序分析

我们如何使用FIFO呢?这个程序举了很好的例子

  1. 使用open函数打开管道文件
    如果一个进程以只读(只写)打开,那么这个进程会被阻塞到open,直到另一个进程以只写(只读)或者读写。
  2. 使用read函数读取内容
    read读取普通文件,read不会阻塞。而read读取管道文件,read会阻塞运行,直到管道中有数据或者所有的写端关闭。
  3. 使用write函数发送内容,使用close函数关闭打开的文件。

这两个程序是一个典型的生产者与消费者的模型。两个程序都以阻塞模式使用FIFO。我们首先启动生产者的程序,他会阻塞,等一个读取端打开FIFO。当消费者被启动时,写入端就会停止阻塞并且开始向管道写入数据。同时,读取端开始由管道读取数据。两个程序使用的都是阻塞模式的FIFO,首先启动(写进程/生产者),它将阻塞以等待读进程打开这个(消费者)启动以后,写进程解除阻塞并开始向管道写数据。同时,读进程也开始从管道中读取数据。linux会安排好这两个进程之间的调度,使它们在可以运行的时候运行,在不能运行的时候阻塞.因此,写进程将在管道满时阻塞,读进程将在管道空时阻塞。

二、学习心得

陌生的内容好多……麻了……一整天都在整这玩意……

麻了……

麻了……

麻了……
麻了……

#######麻了……
########麻了……

posted @ 2022-11-10 00:01  acacacac  阅读(87)  评论(0编辑  收藏  举报