2005年九月月 发布的文章

理解dup和dup2

看到ChinaUnix(CU)上的一个帖子后,觉得自己对dup和dup2特别是后者的理解还是有欠缺的,这两个接口看起来很简单,但是理解起来也真的并不是那么容易。

相信大部分在Unix/Linux下编程的程序员手头上都有《Unix环境高级编程》(APUE)这本超级经典巨著。作者在该书中讲解dup/dup2之前曾经讲过“文件共享”,这对理解dup/dup2还是很有帮助的。这里做简单摘录以备在后面的分析中使用:
Stevens said:
(1) 每个进程在进程表中都有一个记录项,每个记录项中有一张打开文件描述符表,可将视为一个矢量,每个描述符占用一项。与每个文件描述符相关联的是:
   (a) 文件描述符标志。
   (b) 指向一个文件表项的指针。
(2) 内核为所有打开文件维持一张文件表。每个文件表项包含:
   (a) 文件状态标志(读、写、增写、同步、非阻塞等)。
   (b) 当前文件位移量。
   (c) 指向该文件v节点表项的指针。
图示:
   文件描述符表
   ————
fd0  0   | p0  ————-> 文件表0 ———> vnode0
   ————
fd1  1   | p1  ————-> 文件表1 ———> vnode1
   ————
fd2  2   | p2 
   ————
fd3  3   | p3 
   ————
… …
… …
   ————

一、单个进程内的dup和dup2
假设进程A拥有一个已打开的文件描述符fd3,它的状态如下:
  进程A的文件描述符表(before dup2)
   ————
fd0  0   | p0 
   ————
fd1  1   | p1  ————-> 文件表1 ———> vnode1
   ————
fd2  2   | p2 
   ————
fd3  3   | p3  ————-> 文件表2 ———> vnode2
   ————
… …
… …
   ————

经下面调用:
n_fd = dup2(fd3, STDOUT_FILENO);后进程状态如下:

进程A的文件描述符表(after dup2)
   ————
fd0  0   | p0 
   ————
n_fd 1   | p1  ————
   ————               \
fd2  2   | p2                 \
   ————                 _\|
fd3  3   | p3  ————-> 文件表2 ———> vnode2
   ————
… …
… …
   ————
解释如下:
n_fd = dup2(fd3, STDOUT_FILENO)表示n_fd与fd3共享一个文件表项(它们的文件表指针指向同一个文件表项),n_fd在文件描述符表中的位置为STDOUT_FILENO的位置,而原先的STDOUT_FILENO所指向的文件表项被关闭,我觉得上图应该很清晰的反映出这点。按照上面的解释我们就可以解释CU中提出的一些问题:
(1) "dup2的第一个参数是不是必须为已打开的合法filedes?" — 答案:必须。
(2) "dup2的第二个参数可以是任意合法范围的filedes值么?" — 答案:可以,在Unix其取值区间为[0,255]。

另外感觉理解dup2的一个好方法就是把fd看成一个结构体类型,就如上面图形中画的那样,我们不妨把之定义为:
struct fd_t {
 int index;
 filelistitem *ptr;
};
然后dup2匹配index,修改ptr,完成dup2操作。

在学习dup2时总是碰到“重定向”一词,上图完成的就是一个“从标准输出到文件的重定向”,经过dup2后进程A的任何目标为STDOUT_FILENO的I/O操作如printf等,其数据都将流入fd3所对应的文件中。下面是一个例子程序:
#define TESTSTR "Hello dup2\n"
int main() {
        int     fd3;

        fd3 = open("testdup2.dat", 0666);
        if (fd < 0) {
                printf("open error\n");
                exit(-1);
        }

        if (dup2(fd3, STDOUT_FILENO) < 0) {       
                printf("err in dup2\n");
        }
        printf(TESTSTR);
        return 0;
}
其结果就是你在testdup2.dat中看到"Hello dup2"。

二、重定向后恢复
CU上有这样一个帖子,就是如何在重定向后再恢复原来的状态?首先大家都能想到要保存重定向前的文件描述符。那么如何来保存呢,象下面这样行么?
int s_fd = STDOUT_FILENO;
int n_fd = dup2(fd3, STDOUT_FILENO);
还是这样可以呢?
int s_fd = dup(STDOUT_FILENO);
int n_fd = dup2(fd3, STDOUT_FILENO);
这两种方法的区别到底在哪呢?答案是第二种方案才是正确的,分析如下:按照第一种方法,我们仅仅在"表面上"保存了相当于fd_t(按照我前面说的理解方法)中的index,而在调用dup2之后,ptr所指向的文件表项由于计数值已为零而被关闭了,我们如果再调用dup2(s_fd, fd3)就会出错(出错原因上面有解释)。而第二种方法我们首先做一下复制,复制后的状态如下图所示:
进程A的文件描述符表(after dup)
   ————
fd0  0   | p0 
   ————
fd1  1   | p1  ————-> 文件表1 ———> vnode1
   ————                 /|
fd2  2   | p2               /
   ————             /
fd3  3   | p3  ————-> 文件表2 ———> vnode2
   ————          /
s_fd 4   | p4  ——/ 
   ————
… …
… …
   ————

调用dup2后状态为:
进程A的文件描述符表(after dup2)
   ————
fd0  0   | p0 
   ————
n_fd 1   | p1  ————
   ————               \
fd2  2   | p2                \
   ————                _\|
fd3  3   | p3  ————-> 文件表2 ———> vnode2
   ————
s_fd 4   | p4  ————->文件表1 ———> vnode1
   ————
… …
… …
   ————
dup(fd)的语意是返回的新的文件描述符与fd共享一个文件表项。就如after dup图中的s_fd和fd1共享文件表1一样。

确定第二个方案后重定向后的恢复就很容易了,只需调用dup2(s_fd, n_fd);即可。下面是一个完整的例子程序:
#define TESTSTR "Hello dup2\n"
#define SIZEOFTESTSTR 11

int main() {
        int     fd3;
        int     s_fd;
        int     n_fd;

        fd3 = open("testdup2.dat", 0666);
        if (fd3 < 0) {
                printf("open error\n");
                exit(-1);
        }

        /* 复制标准输出描述符 */
        s_fd = dup(STDOUT_FILENO);
        if (s_fd < 0) {
                printf("err in dup\n");
        }

        /* 重定向标准输出到文件 */
        n_fd = dup2(fd3, STDOUT_FILENO);
        if (n_fd < 0) {
                printf("err in dup2\n");
        }
        write(STDOUT_FILENO, TESTSTR, SIZEOFTESTSTR);   /* 写入testdup2.dat中 */

        /* 重定向恢复标准输出 */
        if (dup2(s_fd, n_fd) < 0) {
                printf("err in dup2\n");
        }
        write(STDOUT_FILENO, TESTSTR, SIZEOFTESTSTR); /* 输出到屏幕上 */
        return 0;
}
注意这里我在输出数据的时候我是用了不带缓冲的write库函数,如果使用带缓冲区的printf,则最终结果为屏幕上输出两行"Hello dup2",而文件testdup2.dat中为空,原因就是缓冲区作怪,由于最终的目标是屏幕,所以程序最后将缓冲区的内容都输出到屏幕。

三、父子进程间的dup/dup2
由fork调用得到的子进程和父进程的相同文件描述符共享同一文件表项,如下图所示:
父进程A的文件描述符表
   ————
fd0  0   | p0 
   ————
fd1  1   | p1  ————-> 文件表1 ———> vnode1
   ————                            /|\
fd2  2   | p2                            |
   ————                             |
                                              |
子进程B的文件描述符表                |
   ————                             |
fd0  0   | p0                            |
   ————                             |
fd1  1   | p1  ———————|
   ————
fd2  2   | p2 
   ————
所以恰当的利用dup2和dup可以在父子进程之间建立一条“沟通的桥梁”。这里不详述。

四、小结
灵活的利用dup/dup2可以给你带来很多强大的功能,花了一些时间总结出上面那么多,不知道自己理解的是否透彻,只能在以后的实践中慢慢探索了。

参考资料:
1、《Unix环境高级编程》

改格

又是一年中秋节,大街小巷弥漫着月饼的味道和喜庆的气氛,发现现在中秋的一个特点就是“月饼贼贵,人还排队(买)”,看来中国人民的生活水平真是提高了。这是我在沈城过的第二个中秋,对于有GF的我中秋节意味着“大出血”,所以今天在沈阳最繁华的商业街上你要是细心观察的话准会发现我们的身影(如果你真的这么做的话,你就应该到医院看医生了:),小心现在医院贼宰人哟,先看看钱袋里是否带足钱了再说)。不过今年中秋有一个不同之处就是GF让我“改格”。从到公司那天起我就一直保持着一个风格–“正式”–几乎每天都着正装,当然我也尽量收敛变通一些,毕竟不能穿的太正式,否则就与身份不相称了。但是我的的确确是从内心喜欢这种风格,这是我一直追求的生活的一部分。着正装的目的不是为了炫耀着什么,曾经有位同事和我说过,大致就是“如果你要做什么样的人,你就要在每件事上模仿这样的人的做法”。

直到中秋节的前一天晚上,突然GF说我“打扮得太老了,和我的实际年龄不符”,我愕然。GF随之下了命令“你要改变风格”–改格的由来。帝制都被推翻100多年了,不过这个世界仍然有“圣旨”这个东西的存在,有GF的单身的男士一般都知道:)。从东到西,从西到东,12个小时我们一个接一个的与Tony Jean、马克.华菲和Jack Jones不期而遇。最后的交易不便透露,不过最终离彻底改变风格还差一步,我们已经筋疲力尽。“改格尚未成功,我们还需努力”,这一艰巨的任务恐怕只有在下周某某大型商场的“狂甩节”上才能完成了。

回家,在环路上,突然防空警报四起,大街上的车辆同时鸣笛,旁边的几个中年人谈到今天是9.18。我遂从梦中醒来,心中顿生澎湃,第一次身临其境的感受9.18耻辱纪念日,脑中不断浮现出日本鬼子的暴行,看着车上那些年轻人仍然在嬉戏打闹,心里真不是滋味儿…,此时此刻大学军训时常唱的一首歌“大刀向鬼子头上砍去…”从我的手机中飘扬出来…

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats