05 进程间通信

这一章中我们来学习如何使多个进程间有效地通信。由于这些通信方式中很多都基于文件等持久存储,我们将从基本的 I/O 操作讲起,夯实基础后再带你认识管道、信号等多种进程间通信的方法。

进程间通信

在前面两章中我们讲完了操作系统中非常重要的一部分内容——内存管理。我们已经提到了,在现代的计算机中,进程的地址空间和物理内存是区别开的,除非一个进程与其它进程共享一段内存,否则不同的进程是不能够使用彼此的地址空间的。一些同学肯定已经想到了,如果不同的进程之间不能共享内存,那么它们互相之间该如何通信呢?比如我写了两个程序 A 和 B,B 需要将 A 的输出值作为输入值运行,那我怎么才能达到这个目的呢?

在前面的课程中我们已经学习了fork()(,我们可以通过这个系统调用产生一个子进程,然后用 exec()在子进程中执行另一个程序,在父进程中调用wait()等待进程运行完毕,但这样我们在父进程中只能获得进程的退岀码,这之外的数据我们就无法获得了;
而随着子迸程的终止,子进程的地址空间也会被系统回收,我们就更无法获得它的数据了。为了能够读取这个数据,我们只有把它存储在一个两个进程都能读取、且不会随着进程的终止而被回收的存储位置,这个位置就是外存。

还记得我们在第一章中提到的文件的抽象吗?文件是对外存中存储的数据的抽象,我们可以利用一个文件进行 进程间的通信(Inter-process Communication,IPC。进程 A 终止前将输出值写入这个文件,然后进程 B 再将这个文件的内容作为输入值读取进来,开始运行。这是进程间通信的一种常见方法;实际上,一段内存的共享也是通过将一个共享的文件同时映射到两个进程的地址空间实现的。

另一种进程间通信的方法就是 信号(Signal)。信号类似于异常和中断,是异步的;进程在接到信号后在内核态通过对应的信号处理函数来处理该信号。在这一章中我们会先讲解系统对 I/O 和文件的处理,然后再以这些知识为基础去讲解通过文件进行进程间通信的方法,最后再讲到信号。

基本IO操作

文件到底是什么?
我们已经提到,文件是对于外存中存储的数据的抽象,而外存实际上就是磁盘(disk),固态硬盘(SSD,Solid State Drives),磁带(tape)等物理存储设备。这些存储设备与鼠标、键盘、屏幕等无异,都属于 I/O 设备,你可以向这些设备里输入数据,或从这些硬件中获得我们想要的输出数据。因此,从文件中读取数据或向文件中写入数据实际都属于 I/O 的范畴。

正如我们前面提到的,I/O 设备是多种多样的,如果我们需要针对每个 I/O 设备的特点写一段不同的代码,那工程量之浩大可想而知。好在操作系统给我们提供了非常便捷的抽象层——无论我们想要使用什么 I/O 设备,我们都可以调用同一组系统调用,这就是我们接下来要讲到的基本 I/O 操作。

我们要讲到的第一个系统调用是open()。顾名思义,它是用来打开一个文件的,在每次读写文件以前都必须调用这个函数打开文件,获得一个代表该文件的文件标识符,然后再对文件标识符进行操作。与你可能见过的fopen()相比,它处于系统中一个更低的抽象层,你可以对文件进行更基础的操作,但也失去了如fgets(), fscanf()这些方便的库函数的帮助。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char* pathname, int flags, mode_t mode);

这个函数使用三个参数:

  • pathname是被打开文件的路径;
  • flags表示的是这次打开文件所需要进行的操作;
  • mode可以被省略,只有在创建一个文件的时候才会被需要,表示的是新建文件的使用权限。

flagmode都有很多可能的值。这里我们只介绍几个常用的,如果你想了解所有的可能值,你可以到http://linux.die.net/man/ 去看一看。对于这两者你都可以同时同时选择多个值,用或运算连接起来。

mode表示的是被创建的新文件的使用权限,它有固定的格式:

  • 前三位都是 S_I

  • 从第四位开始表示权限

  • 如果它表示只有一个权限,则第四位为该权限的缩写(R 表示 read,读,W 表示 write,写,X 表示 execute,执行),后三位为权限的对象(USR 表示 user,用户,GRP 表示用户 group,组,OTH 表示 others,其他用户)。

  • 如果它表示三种权限都具备,那么第四位到第六位就是 RWX,最后一位表示权限的对象(U 表示用户,G 表示组,O 表示其他)。

文件描述与dup

现在你已经知道怎么调用 open() 了,现在我们就来看看我们能如何利用 open() 返回给我们的 文件描述符(file descriptor)。一个文件描述符就是一个整数,用来代表一个被打开的文件。每个文件描述符都对应一个文件内指针,表示这个被打开文件的实例中指针的位置,也就是说如果一个文件被同一个进程打开
了多次,那么这几个文件描述符中的指针位置可能是不同的。

每个系统都对一个进程可以同时打开的文件数量有限制;每个进程都有一个由文件描述符指向文件的 文件描述符表(file descriptor table)。 open() 每次一般会把新打开的文件放到这个表格中的某个空行,然后返回这个文件描述符。需要注意的是,这个文件描述符只是这个进程中代表这个这个文件的描述符,其它进程即使打开同一个文件也可能有不同的文件描述符;只有由 fork() 产生的子进程才会有和父进程一样的文件描述符。

文件描述符从0 开始,但我们不能使用前三个文件描述符,因为它们是事先被规定好的: 0代表标准输入,1 代表标准输出, 2代表标准错误。这三个文件描述符在进程初始时就已经被打开,你可以通过这些文件描述符从标准输入读取内容,或向标准输出和标准错误写入内容。

标准输出正是printf()输出的对象,而标准输入就是你在命令行中输入的内容,因此当我们想把一个进程的输出值导入到一个文件里的时候,我们只需要修改 1,2 这两个文件描述符,使他们输出到我们指定的文件中,我们再用其他进程来读取这个文件。能够实现上述功能的就是下面这个系统调用:

#include <unistd.h>
int dup2(int oldfd, int newfd);

dup2() 能够使 newfd 指向 oldfd 指向的文件;如果 newfd 本来对应着其他的文件,那么就关掉原来的文件,再使它指向 oldfd 指向的文件。通过调用 dup2() ,我们可以把标准输出和标准错误都关掉,而将 1,2文件描述符替换为我们想要的文件,这样 printf() 的内容就会直接导入到我们想要的文件里。

但是这样我们就面临着一个问题——如果我想在一段时间后重新向标准输出输出内容,那我该怎么重新把1 设定成标准输出呢?为了解决这个问题,我们需要另一个系统调用:

#include <unistd.h>
int dup(int oldfd);

dup() 会选择最小的空闲文件描述符,使它指向 oldfd 指向的文件,它返回的是新的指向这个文件的描述符。这样在调用 dup2() 以前,我们可以先用 dup() 复制一个指向标准输出的文件描述符,然后用 dup2()关闭原来的标准输出的文件描述符。
在应用 dup() , dup2() 和 open() 时,我们都不能忘掉检查函数调用确实成功——这三个函数在运行产生错误时,会返回 −1。养成良好的习惯可以大大减少你花在“抓虫”上的时间。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,904评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,581评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,527评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,463评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,546评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,572评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,582评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,330评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,776评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,087评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,257评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,923评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,571评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,192评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,436评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,145评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容