
管道又分:
1、无名管道:无名管道只能用于有亲缘关系的进程。
2、有名管道:有名管道用于任意两进程间通信。
你就可以把管道理解成位于进程内核空间的“文件”。
给文件加引号,是因为它和文件确实很像,因为它也有描述符。但是它确实又不是普通的本地文件,而是一种抽象的存在。
当进程使用 pipe 函数,就可以打开位于内核中的这个特殊“文件”。同时 pipe 函数会返回两个描述符,一个用于读,一个用于写。如果你使用 fstat函数来测试该描述符,可以发现此文件类型为 FIFO。
而无名管道的无名,指的就是这个虚幻的“文件”,它没有名字。本质上,pipe 函数会在进程内核空间申请一块内存(比如一个内存页,一般是 4KB),然后把这块内存当成一个先进先出(FIFO)的循环队列来存取数据,这一切都由 *** 作系统帮助我们实现了。
pipe 函数打开的文件描述符是通过参数(数组)传递出来的,而返回值表示打开成功(0)或失败(-1)。
它的参数是一个大小为 2 的数组。此数组的第 0 个元素用来接收以读的方式打开的描述符,而第 1 个元素用来接收以写的方式打开的描述符。也就是说,pipefd[0] 是用于读的,而 pipefd[1] 是用于写的。
打开了文件描述符后,就可以使用 read(pipefd[0]) 和 write(pipefd[1]) 来读写数据了。
注意事项
1、这两个分别用于读写的描述符必须同时打开才行,否则会出问题。
2、如果关闭读 (close(pipefd[0])) 端保留写端,继续向写端 (pipefd[1]) 端写数据(write 函数)的进程会收到 SIGPIPE 信号。
3、如果关闭写 (close(pipefd[1])) 端保留读端,继续向读端 (pipefd[0]) 端读数据(read 函数),read 函数会返回 0。
当在进程用 pipe 函数打开两个描述符后,我们可以 fork 出一个子进程。这样,子进程也会继承这两个描述符,而且这两个文件描述符的引用计数会变成 2。
如果你需要父进程向子进程发送数据,那么得把父进程的 pipefd[0] (读端)关闭,而在子进程中把 pipefd[1] 写端关闭,反之亦然。为什么要这样做?实际上是避免出错。传统上 pipe 管道只能用于半双工通信(即一端只能发,不能收;而另一端只能收不能发),为了安全起见,各个进程需要把不用的那一端关闭(本质上是引用计数减 1)。
步骤一:fork 子进程
步骤二:关闭父进程读端,关闭子进程写端
父进程 fork 出一个子进程,通过无名管道向子进程发送字符,子进程收到数据后将字符串中的小写字符转换成大写并输出。
有名管道打破了无名管道的限制,进化出了一个实实在在的 FIFO 类型的文件。这意味着即使没有亲缘关系的进程也可以互相通信了。所以,只要不同的进程打开 FIFO 文件,往此文件读写数据,就可以达到通信的目的。
1、文件属性前面标注的文件类型是 p
2、代表管道文件大小是 0
3、fifo 文件需要有读写两端,否则在打开 fifo 文件时会阻塞
通过命令 mkfifo 创建
通过函数 mkfifo创建
函数返回 0 表示成功,-1 失败。
例如:
cat 命令打印 test文件内容
接下来你的 cat 命令被阻塞住。
开启另一个终端,执行:
然后你会看到被阻塞的 cat 又继续执行完毕,在屏幕打印 “hello world”。如果你反过来执行上面两个命令,会发现先执行的那个总是被阻塞。
有两个程序,分别是发送端 send 和接收端面 recv。程序 send 从标准输入接收字符,并发送到程序 recv,同时 recv 将接收到的字符打印到屏幕。
发送端
接收端
编译
运行
因为 recv 端还没打开test文件,这时候 send 是阻塞状态的。
再开启另一个终端:
这时候 send 端和 recv 端都在终端显示has opend fifo
此时在 send 端输入数据,recv 打印。
管道命令就是用来连接多条指令的,前一条指令的输出流向会作为后一条指令的 *** 作对象。管道命令的 *** 作符是:|,它只能处理由前面一条指令传出的正确输出信息,对错误信息是没有直接处理能力的。然后,传递给下一条指令,作为 *** 作对象。
基本格式:
指令1 | 指令2 | …
【指令1】正确输出,作为【指令2】的输入,然后【指令2】的输出作为【指令3】的输入,如果【指令3】有输出,那么输出就会直接显示在屏幕上面了。通过管道之后【指令1】和【指令2】的正确输出是不显示在屏幕上面的。
【提醒注意】
管道命令只能处理前一条指令的正确输出,不能处理错误输出
管道命令的后一条指令,必须能够接收标准输入流命令才能执行。
使用示例
1、分页显示/etc目录中内容的详细信息
$ ls -l /etc | more
2、将一个字符串输入到一个文件中
$ echo “hello world” | cat >hello.txt
管道用于有学园关系的进程之间。
管道的pipe 系统调用实际上就是创建出来两个文件描述符。
当父进P1程创建出 fd[2] 时,子进程P2 会继承父进程的所有,所以也会得到pipe 的 2个 文件描述符。
所以毫无瓜葛的两个进程,一定不会访问到彼此的pipe。无法用管道进行通信。
管道一般是单工的。f[0]读,f[1]写
管道也可以适用于 兄弟进程(只要有血缘即可)。由于管道是单工的,当两个进程之间需要双向通信,则需要两跟管道。
执行
ctrl-c(2号信号) + SIGUSR1 信号 绑了一个新函数。则 ctrl-c 无效。
查看进程的信号
号信号被捕获。
将2号信号忽略掉
9号信号 kill 和19号信号 stop 不能乱搞,只能用缺省。
其它信号甚至段信号也都可以捕获。
改变程序的执行现场,修改PC指针,有些像goto,只不过返回非0值
运行结果
making segment fault
after segment fault
程序不会死。
如果不忽略 page fault
则会产生 core dump.
不停的给data 赋值,同时每隔1s会有信号进来,打印 data的值。
理论上打印出来的结果同时为0,同时为1
但并非如此,是 0,1,交替随机的。
signal 异步的,随时都可以进来,所以打印出来的结果,并不是我想要的。
信号对于应用程序来说,很像中断对于内核,都是访问临界区数据
信号被屏蔽,延后执行。
写多线程的程序时,不要以为只有线程之间有竞争,其实信号也会有竞争
system v 的IPC 年代有些久远。
有血缘关系的进程 key_t 都是相同的。
Key 是私有key IPV PRIVATE
可能用消息队列,可能用共享内存,可能用信号量进行通讯。
利用 _pathname 路径,约定好一条路径。和tcp/ip地址很像,来生成一个key_t key, 用msg_get shm_get 得到共享内存or 信号量。
int id 可以理解为文件描述符 fd。
其中Sys V 的共享内存 最为常用。
一定要检查路径,如果仅仅有2个进程,你没有创建路径,两者都是 -1(相当于大家约定好了),那当然能通信拉。但更多的进程出现,则会有问题。
一定要检查返回值
依然依靠key,但是api 实在是太挫了。P&V *** 作都是 semop. (posix 的 ipc跟为简洁)
POSIX 共享内存当然也需要一个名字,但并不是路径。
无论读进程还是写进程,都需要传入相同的名字。
如果是unbuntu 会在以下路径生成文件
其实 2和3 是1 的符号链接。 只要保证是一个就能互相通信
关键点,mmap 内存的属性修改为 private 后,产生写时copy,虚拟地址一样,但是物理地址已经不同了
当然 如果子进程修改了程序背景,执行了 exec,那么完全不一样了,直接修改了内存逻辑。
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)