范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

进程间通信(IPC)系列管道(pipe)

  今天从源码的角度分析下进程间通信之管道(pipe )。
  什么是管道 ?
  所谓管道,是指用于连接一个读进程和一个写进程,以实现它们之间通信的共享文件,又称 pipe  文件。
  向管道(共享文件)提供输入的发送进程(即写进程),以字符流形式将大量的数据送入管道;而接收管道输出的接收进程(即读进程),可从管道中接收数据。由于发送进程和接收进程是利用管道进行通信的,故又称管道通信。
  为了协调双方的通信,管道通信机制必须提供以下3 方面的协调能力。互斥。当一个进程正在对 pipe  进行读/写操作时,另一个进程必须等待。同步。当写(输入)进程把一定数量(如4KB)数据写入 pipe  后,便去睡眠等待,直到读(输出)进程取走数据后,再把它唤醒。当读进程读到一空 pipe  时,也应睡眠等待,直至写进程将数据写入管道后,才将它唤醒。对方是否存在。只有确定对方已存在时,才能进行通信。
  管道的应用
  管道是利用 pipe()  系统调用而不是利用 open()  系统调用建立的。pipe()   调用的原型是:int pipe(int fd[2])
  我们看到,有两个文件描述符与管道结合在一起,一个文件描述符用于管道的read()  端,一个文件描述符用于管道的 write()  端。
  由于一个函数调用不能返回两个值,pipe()  的参数是指向两个元素的整型数组的指针,它将由调用两个所要求的文件描述符填入。
  fd[0]  元素将含有管道 read()  端的文件描述符,而 fd[1]  含有管道 write()  端的文件描述符。系统可根据 fd[0]  和 fd[1]  分别找到对应的 file  结构。
  注意,在 pipe  的参数中,没有路径名,这表明,创建管道并不像创建文件一样,要为它创建一个目录连接。这样做的好处是,其他现存的进程无法得到该管道的文件描述符,从而不能访问它。
  那么,两个进程如何使用一个管道来通信呢?
  我们知道,fork()  和 exec()  系统调用可以保证文件描述符的复制品既可供双亲进程使用,也可供它的子女进程使用。也就是说,一个进程用 pipe()  系统调用创建管道,然后用 fork()  调用创建一个或多个进程,那么,管道的文件描述符将可供所有这些进程使用。
  这里更明确的含义是:一个普通的管道仅可供具有共同祖先的两个进程之间共享,并且这个祖先必须已经建立了供它们使用的管道。
  注意,在管道中的数据始终以和写数据相同的次序来进行读,这表示 lseek() 系统调用对管道不起作用。
  下面给出在两个进程之间设置和使用管道的简单程序:
  #include  #include  #include   int main(void) { int fd[2], nbytes; pid_t childpid; char string[] = "Hello, world! "; char readbuffer[80];  pipe(fd);  if((childpid = fork()) == -1) { printf("Error:fork"); exit(1); }  if(childpid == 0) /* 子进程是管道的读进程 */ { close(fd[1]); /*关闭管道的写端 */ nbytes = read(fd[0], readbuffer, sizeof(readbuffer)); printf("Received string: %s", readbuffer); exit(0); } else /* 父进程是管道的写进程 */ { close(fd[0]); /*关闭管道的读端 */ write(fd[1], string, strlen(string));  }  return(0); }
  注意,在这个例子中,为什么这两个进程都关闭它所不需的管道端呢?
  这是因为写进程完全关闭管道端时,文件结束的条件被正确地传递给读进程。而读进程完全关闭管道端时,写进程无需等待继续写数据。
  阻塞读和写分别成为对空和满管道的默认操作,这些默认操作也可以改变,这就需要调用 fcntl()  系统调用,对管道文件描述符设置 O_NONBLOCK  标志可以忽略默认操作:#include   fcntl(fd,F_SETFL,O_NONBlOCK);
  上述例子如下图:
  管道的源码分析
  pipefs 初始化
  pipefs  是一种简单的、虚拟的文件系统类型,因为它没有对应的物理设备,因此其安装时不需要块设备。大部分文件系统是以模块的形式来实现的。该文件系统相关的代码在 fs/pipe.c  中:
  static struct file_system_type pipe_fs_type = { .name = "pipefs", .get_sb = pipefs_get_sb, .kill_sb = kill_anon_super, };   static int __init init_pipe_fs(void) { /* pipe_fs_type 链接到file_systems 链表可以通过读/proc/filesystems 找到  "pipefs"入口点,在那里,"nodev"标志表示没有设置 FS_REQUIRES_DEV 标志,即该文件系统没有对应的物理设备。 */ int err = register_filesystem(&pipe_fs_type); if (!err) { //安装pipefs 文件系统 pipe_mnt = kern_mount(&pipe_fs_type); if (IS_ERR(pipe_mnt)) { err = PTR_ERR(pipe_mnt); unregister_filesystem(&pipe_fs_type); } }  return err; }   //pipefs 文件系统是作为一个模块来安装的 fs_initcall(init_pipe_fs); module_exit(exit_pipe_fs); //模块卸载函数
  上述就是初始化时注册 pipefs  文件系统的过程,操作如下:把 pipe_fs_type  链接到 file_systems  链表中。创建一个struct vfsmount  结构,然后把该结构赋值给全局变量pipe_mnt 。该 vfsmount  结构通过 super_block  与 pipefs  文件系统进行了关联。
  pipe 的创建
  pipefs  文件系统的入口点就是pipe() 系统调用,其内核实现函数为sys_pipe() ,而真正的工作是调用 do_pipe()  函数来完成的,其代码在 fs/pipe.c  中: int do_pipe(int *fd) {     struct file *fw, *fr;     int fdw, fdr;      //创建管道写端的file结构     fw = create_write_pipe();         //在写端的file结构基础上构建读端     fr = create_read_pipe(fw);      //创建读端fd     fdr = get_unused_fd();      //创建写端fd     fdw = get_unused_fd();      //fd 和 file进行关联     fd_install(fdr, fr);     fd_install(fdw, fw);      //返回读写端fd     fd[0] = fdr;     fd[1] = fdw;      ...      return 0; }   struct file *create_write_pipe(void) {     ...      struct qstr name = { .name = "" };      //创建 file 结构     f = get_empty_filp();      //创建一个pipe相关的 inode     inode = get_pipe_inode();      //创建一个dentry结构     dentry = d_alloc(pipe_mnt->mnt_sb->s_root, &name);      //inode 和 dentry 相关联     d_instantiate(dentry, inode);      // pipe 和 pipe_mnt 关联     f->f_path.mnt = mntget(pipe_mnt);      //file 与 dentry 相关联     f->f_path.dentry = dentry;      //该file是只写的     f->f_flags = O_WRONLY;      //该pipe的可操作方法     f->f_op = &write_pipe_fops;      ...      return f; }    struct file *create_read_pipe(struct file *wrf) {     //创建一个file结构用于读     struct file *f = get_empty_filp();      //file 与 已有的dentry、inode、struct vfsmount 相关联     f->f_path.mnt = mntget(wrf->f_path.mnt);     f->f_path.dentry = dget(wrf->f_path.dentry);     f->f_mapping = wrf->f_path.dentry->d_inode->i_mapping;      //该file是只读的     f->f_flags = O_RDONLY;     f->f_op = &read_pipe_fops;      f->f_mode = FMODE_READ;     f->f_version = 0;      return f; }
  do_pipe()  的操作也很简单,操作如下:创建管道的读写端关联的 file 、inode 、dentry  结构。把 fd  和 file  进行联系,并返回给用户关于管道的读写描述符。创建一个 pipe_inode_info  对象,该结构指向具体的数据页内存缓冲区。后续所有向管道的读写均操作于该数据页内存缓冲区中。
  上述 pipe  的初始化中,创建的 pipe_inode_info  对象记录了 pipe  读写过程中所有的得数据。pipe_inode_info  对象的结构如下struct pipe_inode_info { wait_queue_head_t wait;//存储等待读写进程的等待队列 /* nrbufs: 写入但还未被读取的数据占用缓冲区的页数 curbuf:当前正在读取环形缓冲区中的页节点 */ unsigned int nrbufs, curbuf; struct page *tmp_page; //临时缓冲区页面 unsigned int readers; //正在读取pipe的读进程数目 unsigned int writers; //正在写pipe的写进程数目 unsigned int waiting_writers; //等待管道可以写的进程数目 ... struct inode *inode; //pipe 对应的inode结构 struct pipe_buffer bufs[PIPE_BUFFERS]; //环形缓冲区,每个元素对应一个内存页 };
  经过上述的一系列初始化,整个管道的内存结构如下图所示
  pipe_read
  static ssize_t pipe_read(struct kiocb *iocb, const struct iovec *_iov, unsigned long nr_segs, loff_t pos) { ...  //要读的数据长度 total_len = iov_length(iov, nr_segs);  do_wakeup = 0; ret = 0; //读之前先加锁 mutex_lock(&inode->i_mutex);  //循环读数据 for (;;) { int bufs = pipe->nrbufs; if (bufs) { int curbuf = pipe->curbuf; struct pipe_buffer *buf = pipe->bufs + curbuf; size_t chars = buf->len;  //待读的数据比要读的数据多,则设置要读的长度 if (chars > total_len) chars = total_len;  error = ops->confirm(pipe, buf); if (error) { if (!ret) error = ret; break; }  atomic = !iov_fault_in_pages_write(iov, chars); redo:  //把数据拷贝到用户缓冲区 addr = ops->map(pipe, buf, atomic); error = pipe_iov_copy_to_user(iov, addr + buf->offset, chars, atomic); ops->unmap(pipe, buf, addr); if (unlikely(error)) { ... }  ret += chars; buf->offset += chars; buf->len -= chars; //该页的数据全部读完,释放该page if (!buf->len) { buf->ops = NULL; ops->release(pipe, buf); curbuf = (curbuf + 1) & (PIPE_BUFFERS-1); pipe->curbuf = curbuf; pipe->nrbufs = --bufs; do_wakeup = 1; } total_len -= chars; if (!total_len) break; /* common path: read succeeded */ } //if (bufs) //若该页没读完,继续循环读,若该page读完,则读下一个page if (bufs) /* More to do? */ continue;  if (!pipe->writers) break;  if (!pipe->waiting_writers) { if (ret) break; //非阻塞跳出循环,不进行休眠 if (filp->f_flags & O_NONBLOCK) { ret = -EAGAIN; break; } }  if (signal_pending(current)) { if (!ret) ret = -ERESTARTSYS; break; } //唤醒写进程 if (do_wakeup) { wake_up_interruptible_sync(&pipe->wait); kill_fasync(&pipe->fasync_writers, SIGIO,POLL_OUT); }  //让出cpu,进行休眠,等待条件唤醒 pipe_wait(pipe);  } // for  mutex_unlock(&inode->i_mutex);  if (do_wakeup) { wake_up_interruptible_sync(&pipe->wait); kill_fasync(&pipe->fasync_writers, SIGIO, POLL_OUT); } if (ret > 0) file_accessed(filp);  return ret; }
  读的操作很简单,操作如下:读之前先锁定内存。从缓冲区中获取数据页,把页中数据拷贝到用户缓冲区中。数据全部被读完,则发送信号唤醒写进程,同时读进程让出 CPU  进行休眠。
  读取数据的过程如下
  pipe_write
  static ssize_t pipe_write(struct kiocb *iocb, const struct iovec *_iov, unsigned long nr_segs, loff_t ppos) { ...  //需要写入数据的长度 total_len = iov_length(iov, nr_segs);  do_wakeup = 0;  mutex_lock(&inode->i_mutex);  //管道读端已关闭,返回SIGPIPE信号 if (!pipe->readers) { send_sig(SIGPIPE, current, 0); ret = -EPIPE; goto out; }  /* We try to merge small writes  curbuf:当前的pipe缓冲节点 nrbufs:非空的pipe缓冲节点数目 buffers:buf缓冲区总数目 buf->offset:页内可用数据的偏移量 buf->len :可用数据的长度 buf->offset + buf->len :页内可以往有效数据后追加数据的下标  */ //获取完整页之外的数量 chars = total_len & (PAGE_SIZE-1); /* size of the last buffer */ if (pipe->nrbufs && chars != 0) { //获取下一个可用的缓冲区,比如缓冲区是0~5, 有效数据起始buff是3,有效数据是4,那么存储buff数据依次为3,4,5,0,下一个可用的buff为1 ((3+4-1)/5) int lastbuf = (pipe->curbuf + pipe->nrbufs - 1) & (PIPE_BUFFERS-1); //获取下一个可用的buff,lastbuf为获取到的索引 struct pipe_buffer *buf = pipe->bufs + lastbuf;  const struct pipe_buf_operations *ops = buf->ops; /*offset: page中数据的偏移量。len:page中数据的长度 目前数据在page中的有效起始地址 + 有效数据长度 = 下一个可存放数据的地址。 管道是从前往后读的,并没规定读写大小,有可能只读取了page的前一部分,中间部分尚未读取,但是写的时候必须从中间有效数据后继续写 */ int offset = buf->offset + buf->len; //当前需要写入的数据 + 已有的数据若没有超过PAGE_SIZE大小,则拷贝到page中 if (ops->can_merge && offset + chars <= PAGE_SIZE) { ... redo1: addr = ops->map(pipe, buf, atomic); //将用户数据拷贝到page中 error = pipe_iov_copy_from_user(offset + addr, iov, chars, atomic); ops->unmap(pipe, buf, addr); ret = error; do_wakeup = 1; if (error) { ... } //更新有效数据 buf->len += chars; total_len -= chars; ret = chars; //全拷贝完则跳出 if (!total_len) goto out; } }  for (;;) { int bufs;  if (!pipe->readers) { ... } /*获取管道还有多少有效的buffer缓冲区*/ bufs = pipe->nrbufs; if (bufs < PIPE_BUFFERS) { //有效的bufs小于缓冲区总数 int newbuf = (pipe->curbuf + bufs) & (PIPE_BUFFERS-1); //获取下一个可用的buf struct pipe_buffer *buf = pipe->bufs + newbuf; struct page *page = pipe->tmp_page;  if (!page) { page = alloc_page(GFP_HIGHUSER); if (unlikely(!page)) { ... } pipe->tmp_page = page; }  do_wakeup = 1; chars = PAGE_SIZE; if (chars > total_len) chars = total_len;  iov_fault_in_pages_read(iov, chars); redo2:  error = pipe_iov_copy_from_user(src, iov, chars, atomic);  ret += chars; //更新有效数据  buf->page = page; buf->ops = &anon_pipe_buf_ops; buf->offset = 0; buf->len = chars; pipe->nrbufs = ++bufs; pipe->tmp_page = NULL;  total_len -= chars; //数据写完,跳出循环结束 if (!total_len) break; } //还有可用的缓冲区,继续写 if (bufs < PIPE_BUFFERS) continue; //缓冲区全部写完了,则判断是否需要阻塞休眠 if (filp->f_flags & O_NONBLOCK) { if (!ret) ret = -EAGAIN; break; }  if (signal_pending(current)) { if (!ret) ret = -ERESTARTSYS; break; } //唤醒读进程 if (do_wakeup) { wake_up_interruptible_sync(&pipe->wait); kill_fasync(&pipe->fasync_readers, SIGIO, POLL_IN); do_wakeup = 0; } //写进程休眠 pipe->waiting_writers++; pipe_wait(pipe);//将当前任务加入到等待列表,释放锁,让出CPU pipe->waiting_writers--; } out: ...  return ret; }
  管道写操作流程如下:写之前先锁定内存。获取可写的缓冲区,若可写则循环写。若不可写,则唤醒读进程,同时写进程进行休眠,让出 CPU 。
  往管道中写数据的过程如下
  管道中最重要的2个方法就是管道的读写。从上述的分析来看,读写进程共同操作内核中的数据缓冲区,若有缓冲区可写,则进程往缓冲区中写,若条件不允许写,则进程休眠让出 CPU 。读操作同理。
  从上述管道读写操作可知,父子进程之所以能够通过 pipe  进行通信,是因为在内核中共同指向了同一个pipe_inode_info  对象,共同操作同一个内存页。
  总结
  管道也称无名管道,是 UNIX  系统中进程间通信(IPC )中的一种。管道由于是无名管道,因此只能在有亲缘关系的进程间使用。管道不是普通的文件,它是基于内存的。管道属于半双工,数据只能从一方流向另一方,也即数据只能从一端写,从另一端读。管道中读数据是一次性的操作,数据读取后就会释放空间,让出空间供更多的数据写。管道写数据遵循先入先出的原则。

见证2017丨首度定义上市公司品牌价值每经记者彭斐每经编辑张海妮多年以来,人们虽然认识到品牌的重要性,但是却很难对其进行量化评估。中国是经济大国,但与经济强国还有不小差距,表现之一便是中国企业的品牌竞争力逊色于经济总量可到基层社区中心解决开药问题避免院内交叉感染央视网消息国务院联防联控机制12月9日下午举行新闻发布会,发布会上,国家卫生健康委相关负责人介绍,正在指导各地相应调整和优化就医流程,尽最大努力来降低医院内交叉感染,保证就诊患者和退役军人胡顶养螃蟹的成功秘诀是勤奋武汉退役军人就业创业故事长江日报大武汉客户端12月9日讯养螃蟹前期需要准备的工作很多,蟹苗只有在海水里才能孵化,我们这边如果大量养殖,就需要到海边城市去进蟹苗。近日,记者在联系胡顶时,他正在为明年的螃蟹养独属于中国潮流艺术的浪漫极次元文化近年来国潮热国货热方兴未艾,中国传统文化焕发出强大的生机与活力。国潮的兴起,意味着一种趋向性的审美导向开始形成。这种审美以中国元素为内核,背后是对中华优秀传统文化的认同。接下来就让个税专项附加扣除覆盖养育学病等全生命周期央视网消息2023年度个税专项附加扣除和2022年相比最大的不同是,专项附加扣除项目在今年3月新纳入了3岁以下婴幼儿照护项目。家里有婴幼儿的父母,今年就需要提前进行填写及确认信息。2023年退休养老金计算,个人账户26。4万元,每月能领取多少钱?退休养老金的计算公式形式上是全国统一的,主要包括基础养老金和个人账户养老金两部分构成,个别人员还会有第三部分过渡性养老金。影响养老金的高低因素有很多,让我们看一下养老金的各个部分是政府带队包机出国抢订单!抢的是啥?政府带队,江浙等多地外贸大军组团抢订单,12月9日凌晨1时,江苏省苏州市组织的招商团队,将包机前往欧洲招商。今年以来,外需走弱订单不足,成为不少外贸企业面临的一大难题。新闻11邀请牙茅业绩股价双杀!通策医疗实控人遭调查,牙科生意不好做了?近一段时间以来,国家层面大力推动种植牙集采。分析认为,集采会对种植牙价格产生较大影响,进而影响到相关企业投资时报研究员习羽牙茅通策医疗(600763。SH)董事长吕建明近期被立案调未来三年,可能让普通人获益的10个行业是什么?方向对了,努力才有意义!如今的社会,每个人都很焦虑,都想要多赚一点钱,可是如果方向不对,就算你每天工作24个小时,也不可能变得富有。说白了,我们还是要做趋势上的事情,因为趋势可以带全年新增22家!烟台莱山经济开发区四上企业入库纳统取得新成效为进一步推进园区四上企业培育入库工作,加快培育新的经济增长点,准确全面及时客观,反映园区经济发展水平,今年以来,烟台莱山经济开发区在主导产业培优做强和集群化发展重点项目精细服务方面13中9轰23分冲天一扣,火箭新星史密斯,打出身价证明战文水清清109118,火箭不敌马刺。两支对战绩没有过多要求之役,孰胜孰负意义不大,球迷更关注的是球员们的表现,小贾伦史密斯13中9三分7中4轰下生涯新高的23分,以及两次上演重天一
捡垃圾官方魔改,奇怪的主板知识增加了一块666主板老观众应该看过我之前发的华硕H110T这块小主板的视频吧,里面提到了虽然是H110芯片组,但是能支持69代的CPU,包括志强和部分的笔记本魔改CPU。支持这么多CPU的这个行为,当阿隆索执教药厂与效力过拜仁有关首次见基米希就知道他将很强直播吧3月19日讯本周日阿隆索将率领勒沃库森与老东家拜仁交手,在接受拜仁官网的采访时他表示三年的效力对他来说是特别的时光,并且也影响了他的执教生涯。阿隆索曾在2014年2017年间油菜花田绕民居春色如画醉游人3月15日,游客在天府观塘项目内品茶。近日,随着气温逐渐升高,成都市天府新区正兴街道天府官塘项目内油菜花绽放,吸引众多游客前来踏青赏花。新华社记者刘坤摄影报道3月15日,游人在天府全球最大新造集装箱船完成舾装出海试航来源人民网图片频道原创稿3月19日,全球最大新造集装箱船鑫福103轮正在离泊。3月19日,全球最大新造集装箱船鑫福103轮正在离泊。3月19日,全球最大新造集装箱船鑫福103轮正在停止造谣!玄彬孙艺珍否认离婚传闻!3月20日,玄彬通过工作室否认了离婚的传闻这是毫无根据的消息,根本不是事实,正在持续进行有关谣言的监控。经过内部讨论后,将采取法律措施。孙艺珍所属经纪公司msteam也表示这太不像趣谈被宫女捂死的晋武帝对于一代英主的北魏孝文帝来说,被出轨的老婆给气死,实在是窝囊至极!不过话说回来,孝文帝被气死好歹算是夫妻关系问题,他要是知道另外一位帝王的死法,在阴曹地府里可能就不会那么憋屈了这位突发,特斯拉与顺丰同城速递小哥驾驶的摩托车猛烈撞击,当场死亡3月16日13时40分许,事发南京浦口区江北浦珠北路处,一辆黑色的特斯拉modelY电动汽车,在通过一个铁路道口时,与顺丰同城速递小哥驾驶的摩托车,发生猛烈地撞击。由于摩托车驾驶员就差发布了!小米13Ultra三证齐全,又一款影像旗舰即将到来最近一段时间,小米新一代超大杯旗舰的相关信息正在陆续曝光。博主数码闲聊站最新的爆料显示,小米13Ultra的三证也齐全了,上半年的影像旗舰集合完毕,各种不同焦段取向的长焦大战要来咯忘掉Siri,AppleWatch也能用上Chatgpt如果说今年以来,互联网圈最火的一件事,那么ChatGpt一定是其中之一。凭借着颠覆人类想象的创造力与回答能力,频频在科技圈刷屏。网上也有了各种使用ChatGpt的方法,而也有团队将AI之旅脱下口罩也哇塞泰国那些难忘的明眸(2)本期还是继为大家带来美丽的AI泰国小姐姐,图片依旧由stablediffusion产出,有需要prompt的请留言,可用SD自己生产哦!也欢迎SD高手不吝赐教,共同进步!!捂脸捂脸香港的宠物情,宠物猫宠物狗的天堂香港是一个现代化的国际都市,拥有丰富的文化和社会资源,也是一个爱狗和爱宠物的城市。每在周末,许多香港人会喜欢带着自己的爱狗到海边散步,享受阳光和海风的美好。这已成为了一种流行的文化