参考资料
1.Linux内核完全注释 v5.0修正版
实验环境
bochs模拟x86硬件平台下的Linux0.12操作系统
实验环境地址:http://www.oldlinux.org/Linux.old/bochs/ ,该路径下选择一个Linux0.12版本就可进行实验。
函数声明
管道的创建是通过sys_pipe()系统调用实现的,其声明如下:
创建管道系统调用。
//fildes用于返回创建的读写文件句柄(描述符)。这对文件句柄指向同一管道i节点。
// 参数:filedes -文件句柄数组。fildes[0]用于读管道数据,fildes[1]向管道写入数据。
// 成功时返回0,出错时返回-1。
int sys_pipe(unsigned long * fildes);
执行流程
- 获取一空闲 i 节点以及一页空闲物理页用作管道缓冲, i 节点的i_size指向该物理页,i_zone[0]用作管道缓冲头指针,i_zone[1]用作管道缓冲尾指针。
- 从struct file file_table[NR_FILE]获取两空闲项,这两项都指向上面申请得到的i节点,其中一项用作读操作,另一项用作写操作。
- 从第一项开始搜索任务结构体的struct file * filp[NR_OPEN]以获取两空闲项,并让这两项分别指向上面得到的file结构体。
- 搜索filp得到的第一个空闲项用作管道读句柄,第二个用作管道写句柄。
注意:在申请管道句柄时并没有置上close_on_exec,这也就意味着管道句柄在子进程执行execve后仍然有效。
代码测试
测试内容:父进程创建管道并向管道写数据,子进程执行execve函数后从管道读数据。
/* @file pipe.c */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
void warn(char *s)
{
printf("%s,errno:%d\n",s,errno);
}
/* 父进程向管道写的数据 */
char display[]="hello world";
#define display_len (sizeof(display))
/* 子进程从管道读出数据时所用的缓冲 */
char display2[display_len];
void main(int argc,char **argv)
{
/* 存放申请的管道句柄 */
int fd[2];
/* 用于构建子进程执行execve时所需要的参数char **argv */
char child_argv1[2]={0,0};
char child_argv2[2]={0,0};
char *child_argv[3]={"",child_argv1,child_argv2};
/* 打印下执行的进程,以此区分是子进程还是父进程在执行 */
printf("pid:%d\n",getpid());
/* 执行父进程时,我们没有给额外的参数,因此*(argv+1)应该 */
/* 为null,不为null说明是子进程。 */
if(*(argv+1))
{
printf("child *********************\n");
printf("pid:%d\n",getpid());
/* 解析子进程执行execve函数时给定的参数 */
/* 这两个参数分别是管道读、写句柄 */
fd[0]=(int)**(argv+1);
fd[1]=(int)**(argv+2);
/* 查看这两个句柄是否和父进程的一致 */
printf("read fd:%d,write fd:%d\n",fd[0],fd[1]);
/* 从管道读数据 */
if(read(fd[0],display2,display_len)<0)
{
warn("read error");
}
printf("%s\n",display2);
while(1);
}
/* 创建管道 */
if(pipe(fd)<0)
{
warn("pipe error");
}
/* 查看创建的管道句柄 */
printf("read fd:%d,write fd:%d\n",fd[0],fd[1]);
/* 把读写句柄号作为参数给到子进程 */
child_argv1[0]=fd[0];
child_argv2[0]=fd[1];
/* 向管道写数据 */
write(fd[1],display,display_len);
if(fork()==0)
{
/* 加载子进程执行文件 */
/* 这里的执行文件还是本执行文件,即父进程和子进程是同一执行文件 */
execve(*argv,child_argv,0);
warn("execve error");
}
while(1);
}
执行结果
从执行结果看出:
- 以./pipe执行不要带任何的参数,这样 *(argv+1) 就能以此区分父进程和子进程。
- 父进程pid为15,创建的管道读句柄为3,写句柄为4。
- 子进程pid为16,从参数中解析出的管道句柄和父进程一致,读句柄为3,写句柄为4。
- 子进程成功地从管道读出了相应的数据。