进程间通信-信号-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 =
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)函数。
- main 函数调用 mysleep 函数,后者调用 sigaction 注册了 SIGALRM 信号的处理函数 sig_alrm。
- 调用 alarm(nsecs)设定闹钟。
- 调用 pause 等待,内核切换到别的进程运行。
- nsecs 秒之后,闹钟超时,内核发 SIGALRM 给这个进程。
- 从内核态返回这个进程的用户态之前处理未决信号,发现有 SIGALRM 信号,其处理函数是 sig_alrm。
- 切换到用户态执行 sig_alrm 函数,进入 sig_alrm 函数时 SIGALRM 信号被自动屏蔽,从 sig_alrm 函数返回时 SIGALRM 信号自动解除屏蔽。然后自动执行系统调用 sigreturn 再次进入内核,再返回用户态继续执行进程的主控制流程(main 函数调用的 mysleep 函数)。
- 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呢?这个程序举了很好的例子
- 使用open函数打开管道文件
如果一个进程以只读(只写)打开,那么这个进程会被阻塞到open,直到另一个进程以只写(只读)或者读写。 - 使用read函数读取内容
read读取普通文件,read不会阻塞。而read读取管道文件,read会阻塞运行,直到管道中有数据或者所有的写端关闭。 - 使用write函数发送内容,使用close函数关闭打开的文件。
这两个程序是一个典型的生产者与消费者的模型。两个程序都以阻塞模式使用FIFO。我们首先启动生产者的程序,他会阻塞,等一个读取端打开FIFO。当消费者被启动时,写入端就会停止阻塞并且开始向管道写入数据。同时,读取端开始由管道读取数据。两个程序使用的都是阻塞模式的FIFO,首先启动(写进程/生产者),它将阻塞以等待读进程打开这个(消费者)启动以后,写进程解除阻塞并开始向管道写数据。同时,读进程也开始从管道中读取数据。linux会安排好这两个进程之间的调度,使它们在可以运行的时候运行,在不能运行的时候阻塞.因此,写进程将在管道满时阻塞,读进程将在管道空时阻塞。
二、学习心得
陌生的内容好多……麻了……一整天都在整这玩意……
麻了……
麻了……
麻了……
麻了……
#######麻了……
########麻了……