UNIX 下 C 实现 Pipe Descriptors 映射

翻译整理自 http://unixwiz.net/techtips/remap-pipe-fds.html

在UNIX中,通过pipe()可以创建一对单向的pipe descriptors;通过fork()可以创建一个子进程(即创建一个当前进程的“镜像”),两者初始时状态相同,执行互不干扰。利用这两点,我们可以实现进程间的数据交互,进而可以将一个进程的几个standard POSIX file通过dup2()函数被自定义的pipe所映射。这里主要介绍该实现原理和几个注意点以避免descriptor之间产生冲突引起的I/O errors。

UNIX Pipes

pipe(int fd[2])函数可得到两个file descriptors形成一个单向的pipe,fd[0]固定为读端,fd[1]固定为写端。

单向pipe

通常pipe作为进程间的读写通道,并不会用在单独的一个进程中。

The "usual" way

UNIX pipe由一对file descriptors组成,即一端写,一端读。对于单进程来说并没有意义,但可以作为多个进程之间的数据通道,由于UNIX pipe是单向的,所以在数据交换时常会用到多个pipe。例如两个进程之间需要完成数据的交互,那么就需要两个pipe,即四个file descriptors。
使用举例如下:

...

int writepipe[2] = {-1,-1}, /* parent -> child */
    readpipe [2] = {-1,-1}; /* child -> parent */
pid_t   childpid;

/*------------------------------------------------------------------------
 * CREATE THE PAIR OF PIPES
 *
 * Pipes have two ends but just one direction: to get a two-way
 * conversation you need two pipes. It's an error if we cannot make
 * them both, and we define these macros for easy reference.
 */
writepipe[0] = -1;

if ( pipe(readpipe) < 0  ||  pipe(writepipe) < 0 )
{
    /* FATAL: cannot create pipe */
    /* close readpipe[0] & [1] if necessary */
}

#define PARENT_READ readpipe[0]
#define CHILD_WRITE readpipe[1]
#define CHILD_READ  writepipe[0]
#define PARENT_WRITE    writepipe[1]

if ( (childpid = fork()) < 0)
{
    /* FATAL: cannot fork child */
}
else if ( childpid == 0 )   /* in the child */
{
    close(PARENT_WRITE);
    close(PARENT_READ);

    dup2(CHILD_READ,  0);  close(CHILD_READ);
    dup2(CHILD_WRITE, 1);  close(CHILD_WRITE);

    /* do child stuff */
}
else                /* in the parent */
{
    close(CHILD_READ);
    close(CHILD_WRITE);

    /* do parent stuff */
}

descriptors崩溃

在pipe(fd)执行时,通常会为fd分配两个当前最小且可用的值作为文件描述符放入fd[2]数组;正常情况下不会造成问题,但如果恰好此时FD#0和#1(stdin, stdout)均可用且在该状态下是最小的,那么这两个描述符会被分配到fd中。此时,若进行上述的dup2操作,会产生矛盾,先看下面一段代码:

#define PARENT_READ  pipe1[0]   /* fd#0 */
#define CHILD_WRITE  pipe1[1]   /* fd#1 */
#define CHILD_READ   pipe2[0]   /* fd#2 */
#define PARENT_WRITE pipe2[1]   /* fd#3 */

dup2(CHILD_READ=2,  0);  close(CHILD_READ=2);
dup2(CHILD_WRITE=1, 1);  close(CHILD_WRITE=1);

假如为pipe1分配了 FD#0 #1,为pipe2分配了FD#2 #3,而第一个dup2函数又将pipe2[0](CHILD_READ)指向FD#0,即指向了pipe[0](PARENT_READ)。后台守护进程检测到PARENT_READ不再工作,便会关闭PARENT_READ。接下来CHILD_READ也被关闭,那么此时PARENT进程的read和write操作将会不再有意义。

Getting it right

如果要进行映射的file descriptor的值和目标均不同,(即所有dup2(souce, target)调用中souces和targets的值均不同),那么可以按照上述代码片段1进行file descriptor映射,否则就需要重新设计调用逻辑。
这里我们以上述的#0#1(stdin, stdout)为例,需要考虑souce这一file descriptor的三种情况:

  • 0 - 可能造成冲突
  • 1 - 也可能造成冲突
  • >1 - 安全

考虑到一个pipe的两端(同一个pipe的read和write文件描述符)值是不会相等的,可以通过一个表格来审计所有需要考虑的操作。

#define DUP2CLOSE(oldfd, newfd) ( dup2(oldfd, newfd), close(oldfd) )

每个特定情况下所需的操作如下表:

| read FD (should be 0) | write FD (should be 1) | Action |
| :-----------: | :-------------: | : ----- |
| 0 | 1 | nothing - it's already done |
| >=1 | >1 | DUP2CLOSE(rfd, 0); DUP2CLOSE(wfd, 1); |
| 0 | >1 | DUP2CLOSE(wfd, 1); |
| >1 | 1 | DUP2CLOSE(rfd, 0); |
| >1 | 0 | DUP2CLOSE(wfd, 1); DUP2CLOSE(rfd, 0); |
| 1 | 0 | tmp = dup(wfd); close(wfd); DUP2CLOSE(rfd, 0); DUP2CLOSE(tmp, 1); |

在类似的映射操作中,通过上述遍历情况矩阵的形式审计映射操作会大大减少错误的发生。

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

推荐阅读更多精彩内容