当前位置: 首页 > news >正文

自微网站首页主流网站建设技术

自微网站首页,主流网站建设技术,网站建设案例分析题,游戏推广平台哪个好IPC 是 Linux 编程中一个重要的概念#xff0c;IPC 有多种方式#xff0c;本文主要介绍匿名管道(又称管道、半双工管道)#xff0c;尽管很多人在编程中使用过管道#xff0c;但一些特殊的用法还是鲜有文章涉及#xff0c;本文给出了多个具体的实例#xff0c;每个实例均附… IPC 是 Linux 编程中一个重要的概念IPC 有多种方式本文主要介绍匿名管道(又称管道、半双工管道)尽管很多人在编程中使用过管道但一些特殊的用法还是鲜有文章涉及本文给出了多个具体的实例每个实例均附有完整的源代码本文所有实例在 Ubuntu 20.04 上编译测试通过gcc版本号为9.4.0本文适合 Linux 编程的初学者阅读 1 概述 IPC(Inter-Process Communication) - 进程间通信提供了各种进程间通信的方法在 Linux C 编程中IPC 通常有如下几种方式 半双工管道(Unix Pipe)简称管道又称为匿名管道FIFOs - 又称为命名管道消息队列(Message Queues)信号量集(Semaphore Sets)共享内存(Shared Memory Segments)网络 socket(AF_INET Address Family)全双工管道(AF_UNIX Address family) 本文主要介绍匿名管道(Unix Pipe)的应用场景及使用方法并给出多个附有完整源代码的实例管道又被称为 匿名管道是相对于命名管道而言的匿名管道的通信模式是半双工的所谓半双工指的是在管道中数据流是单方向的当 A 进程和 B 进程之间使用管道进行通信时数据要么从 A 发向 B要么从 B 发向 A在一个管道上不能既有 A 向 B 的数据流又有 B 向 A 的数据流管道还有一个特性就是只能在有亲缘关系的进程间传递消息换句话说只有当两个进程有相同的祖先时才有可能使用管道进行通信。 2. 管道的基本概念 简单地说管道是一种将一个进程的输出连接到另一个进程的输入的方法 管道是最古老的 IPC 工具从最早的 UNIX 操作系统开始就存在了它们提供了进程间单向通信的方法(因此称为半双工) 实际上管道的这个特性广泛应用在 Linux 的命令行上比如下面的命令 ls | sort | lp这条命令实际上就建立了一个管道将 ls 的输出作为 sort 的输入将 sort 的输出作为 lp 的输入数据在匿名管道中运行看上去数据在管道中从左向右单方向流动 管道是在 Linux 内核中实现的很多程序员在 shell 脚本编程中都会频繁使用管道但很少有人会去想管道在 Linux 内核中是如何实现的 当一个进程创建管道时内核会创建两个文件描述符(fd[0] 和 fd[1])供管道使用一个描述符(fd[1])用于将数据写入管道另一个描述符(fd[0])用于从管道中读取数据此时管道的实际用处不大因为创建管道的进程只能使用管道与自身进行通信毫无意义 下图展示了一个进程创建管道后进程与内核的关系 从上图中可以看出以下几点 文件描述符是如何连接在一起的进程通过文件描述符(fd[1])向管道写入数据也能够从文件描述符(fd[0])从管道中读取该数据通过管道传输数据时数据是通过内核流动的在 Linux 下管道在内核内部使用 inode 表示innode 驻留在内核中并不属于一个物理文件系统。 这样建立的管道毫无用处一个进程要自言自语没有必要建立一个管道但是如果创建管道的进程再 fork 出一个子进程由于子进程会从父进程继承管道的描述符这样父子进程之间有可以通过这个管道进行通信了 下图描述了父进程、子进程和内核的关系 从上图中我们可以看到父进程和子进程都可以访问管道的两个文件描述符但是很显然如果父进程和子进程同时向 fd[1] 写入数据一定会造成混乱而且如果父、子进程均向 fd[1] 写入数据当从 fd[0] 读出数据时并无法区分读到的数据是那个进程写入的所以必须要做出抉择这个建立的管道的数据是向那个方向流动从父进程流向子进程还是从子进程流向父进程两个进程必须达成一致否则会出现混乱 为了讨论方便我们假定子进程要做一些事务然后把结果通过管道发送给父进程如下面图示 至此管道已经建立完毕下面就是如何使用管道前面提到过管道的文件描述符使用 inode所以可以使用低级文件 I/O 的系统调用来直接访问管道 向管道中写入数据使用 write() 系统调用从管道中读出数据使用 read() 系统调用 特别提醒系统调用 lseek() 不能在管道中使用。 3 如何用C语言创建管道 使用 pipe() 系统调用可以创建一个管道这个调用需要一个由两个整数组成的数组作为参数调用成功后该数组将包含管道的两个文件描述符系统调用pipe()原型#include unistd.hint pipe(int fd[2]); 返回调用成功返回 0调用失败返回 -1error EMFILE (no free descriptors)EMFILE (system file table is full)EFAULT (fd array is not valid)备注: fd[0] 用于从管道中读取数据, fd[1] 用于向管道中写入数据调用成功后不仅两个管道描述符被建立而且处于打开状态可以直接进行读、写操作再次重申所有通过管道传输的数据都要通过内核下面是使用 pipe() 建立管道的代码#include stdio.h #include unistd.h #include sys/types.hmain() {int fd[2];pipe(fd);.. }前面说过这样建立的管道毫无用处进程自言自语并不需要使用管道要使管道有意义在建立管道后要 fork() 一个子进程#include stdio.h #include unistd.h #include sys/types.hmain() {int fd[2];pid_t childpid;pipe(fd);if ((childpid fork()) -1) {perror(fork);exit(1);}.. }如果父进程要从子进程接收数据父进程应关闭向管道写入的描述符 fd[1]而子进程应该关闭从管道读出的描述符 fd[0]如果父进程要向子进程发送数据则父进程应关闭从管道读出的描述符 fd[0]而子进程应该关闭向管道写入的描述符 fd[1]由于管道描述符在父进程和子进程之间是共享的所以我们要确保关闭掉我们不需要的管道末端从技术上讲如果不需要的管道末端没有关闭则永远不会返回 EOF下面代码假定父进程要从子进程接收数据#include stdio.h #include unistd.h #include sys/types.hmain() {int fd[2];pid_t childpid;pipe(fd);if ((childpid fork()) -1) {perror(fork);exit(1);}if (childpid 0) {/* Child process closes up input side of pipe */close(fd[0]);} else {/* Parent process closes up output side of pipe */close(fd[1]);}.. }如前所述建立了管道以后就可以像对待普通文件描述符一样对待管道描述符源程序pipe.c(点击文件名下载源程序)演示了子进程向父进程发送信息Hello, world! 4 在管道上使用 dup() 大多数已有的 Linux 命令或者自己编写的程序其默认的输入设备往往是 STDIN而输出设备是 STDOUT当我们希望在程序中用某个 Linux 命令处理数据时往往不太好获得命令的输出或者不好把数据传送给这个程序这时候管道可以发挥作用 比如 Linux 命令 sort在没有其它参数时其默认的输入设备就是 STDIN当我们在程序中希望使用 sort 处理一组数据时我们可以设法把 STDIN 连接到管道的输出端这样我们向管道中的一端写入数据时管道的另一端已经启动的 sort 就可以从 STDIN 读到数据并进行处理 系统调用 dup() 和 dup2() 可以帮助我们实现这个想法先看一下这两个系统调用的说明 系统调用: dup(); 原型#include unistd.hint dup(int oldfd); 说明dup() 系统调用创建文件描述符 oldfd 的副本使用编号最小的未使用的文件描述符作为新描述符。 返回调用成功则返回新描述符调用失败则返回 -1errno EBADF (oldfd is not a valid descriptor) EBADF (newfd is out of range)EMFILE (too many descriptors for the process) 备注oldfd 不会被关闭新描述符和 oldfd 都可以使用。系统调用dup2(); 原型#include unistd.hint dup2(int oldfd, int newfd); 说明dup2() 系统调用与 dup() 相似创建文件描述符 oldfd 的副本但它不使用编号最小的未使用文件描述符而是使用 newfd 中指定的文件描述符如果文件描述符 newfd 先前已打开该调用会首先将其关闭然后再使用。 返回调用成功则返回新描述符调用失败则返回 -1errno EBADF (oldfd is not a valid descriptor)EBADF (newfd is out of range)EMFILE (too many descriptors for the process)备注使用 dup2()oldfd 会被关闭在子进程中使用 dup2() 将管道的输出(fd[0])复制到 STDIN 上并关闭 STDIN然后用 exec() 启动 sort 时当 sort 从 STDIN 读入数据时实际上是从管道中读出数据当我们从父进程向管道中写入数据时这个数据将被 sort 读取并处理 为了搞清楚这种用法请自行学习 Linux 命令 sort可以用在线手册 man sort 了解该命令的详细信息 下图或许可以更直观地描述这种使用方法 源程序pipe-dup-stdin.c(点击文件名下载源程序)演示了在管道中使用 dup2() 将 fd[0] 复制到 STDIN 的方法 子进程中把 fd[0] 复制到 STDIN然后启动 sort父进程向管道中写入若干个单词每个单词以 \n 结尾sort 从 STDIN 读入数据实际上是从管道中读入数据所以 sort 程序会对这些单词进行排序并把结果写入文件 sort.log 中程序运行完毕后使用 cat sort.log 可以看到经过排序的单词 同样道理也可以在子进程中把管道的输入端(fd[1])复制到 STDOUT 上这样当子进程中启动的程序向 STDOUT 输出时实际上是在向管道上写入数据 源程序pipe-dup-stdout.c(点击文件名下载源程序)演示了在管道中使用 dup2() 将 fd[1] 复制到 STDOUT 的方法 子进程中把 fd[1] 复制到 STDOUT然后启动 unameuname -r 会输出一个字符串到 STDOUT实际上是写入到了管道中父进程从管道中收到了这个字符串并显示出来 子进程中把管道的输入端复制到 STDOUT 后在子进程中启动任何程序在主进程中通过读取管道都可以轻易地获得这个程序的输出比如我们要知道当前系统的是不是 64 位系统那我们在子进程中启动命令 uname -m如果主进程在管道上读出的内容是 x86_64则系统无疑是64位的。 5 使用管道的简单方法 上面介绍的在程序中使用管道获取一个外部程序的输出(或者向一个外部程序输入数据)的方法看上去不仅繁琐而且绕的弯也比较多其实使用管道还有更为简单的方法 使用标准库函数 popen() 可以很容易地使用管道 库函数popen();原型#include stdio.hFILE *popen (char *command, char *type); 说明popen() 函数通过创建一个管道调用 fork 产生一个子进程执行 shell 运行命令来开启一个进程。 返回调用成功则返回一个标准 I/O 流调用 fork() 或 pipe() 失败则返回 NULL该标准库函数通过内部调用 pipe() 创建匿名管道然后 fork() 一个子进程执行 shell并在 shell 中执行 “command” 参数数据流的方向由第二个参数 type 确定type 可以是 “r” 或 “w”表示读或写不可能两者兼而有之在 Linux 下管道将以 type 参数的第一个字符指定的模式打开如果您将 type 设置为 “rw”该函数会以 “r” (读)模式打开管道。 与直接使用 pipe() 系统调用相比这个库函数为我们做了很多繁琐的工作但却让我们失去了对整个过程的精细控制 该函数直接使用了 Bourne shell(bash), 所以在 command 参数中可以使用 shell 元字符以及元字符扩展(包括通配符) 使用 popen() 创建的管道必须使用 pclose() 关闭popen()/pclose() 与标准文件流I/O函数 fopen()/fclose() 非常相似。 库函数pclose();原型#include stdio.hint pclose(FILE *stream); 说明pclose()函数等待相关进程终止并返回由 wait4() 返回的命令退出状态。 返回返回 wait4() 调用的退出状态码如果 stream 不合法或者 wait() 执行失败则返回 -1备注等待管道进程退出然后关闭文件 I/O 流pclose() 函数对由 popen() 派生的进程执行 wait4()当 wait4() 返回时它会销毁管道和文件流 源程序pipe-popen.c(点击文件名下载源程序)完成与前面的例子 pipe-dup-stdin.c 一样的功能但看上去要简单的多 由于 popen() 使用 shell 来执行命令因此 shell 扩展字符和元字符都可以使用此外使用 popen() 打开管道时可以使用一些高级技术来执行命令例如重定向甚至输出管道以下调用示例分别使用了扩展字符、重定向和输出管道 popen(ls ~scottb, r); popen(sort /tmp/foo, w); popen(sort | uniq | more, w);源程序pipe-popen2.c(点击文件名下载源程序)打开了两个管道一个用于 ls 命令另一个用于 sort 命令 下面这个例子试图编写一个通用的管道程序源程序文件pipe-popen3.c(点击文件名下载源程序) 使用方法为./pipe-popen3 [command] [filename]该程序会首先打开文件 filename然后使用 popen() 以写方式打开 command 管道从 filename 中读出内容并写入管道可以尝试用以下方式测试这个例子./pipe-popen3 sort pipe-popen3.c ./pipe-popen3 cat pipe-popen3.c ./pipe-popen3 more pipe-popen3.c ./pipe-popen3 cat pipe-popen3.c | grep main6 管道的原子操作 所谓“原子操作”是指一个或一系列不可中断的操作就是说一个原子操作一旦开始执行就不能被中断直至执行完毕POSIX 标准规定了管道上原子操作的最大缓冲区大小是 512 字节定义在头文件bits/posix1_lim.h 中#define _POSIX_PIPE_BUF 512根据这一定义如果一次写入/读出管道的操作大于 512 字节操作将是非“原子操作”也就是写入/读出的数据可能会被分割在 Linux 下定义的管道上的原子操作的最大缓冲区大小为4096 字节定义在头文件linux/limits.h 中#define PIPE_BUF 4096 /* # bytes in atomic write to a pipe */显然在我们目前的环境下在管道上进行不大于 4096 字节的读/写操作是原子操作在多进程环境下原子操作对管道的读/写操作非常重要当一个进程写入管道的数据大于阈值时其写入过程中间会中断操作系统会产生进程调度如果这时其它进程也向这个管道写入数据那么写入管道的数据会产生混乱。 7 匿名管道的其它说明 尽管管道是半双工的但是打开两个管道并在子进程中合理地重新分配描述符可以构建出一个类似全双工的管道pipe() 调用必须在 fork() 调用之前进行否则描述符将不会被子进程继承使用匿名管道进行通信的进程都必须有一个共同的祖先而且这个祖先必须是管道的创建者由于管道位于内核中不在管道创建者祖先中的进程都无法对其进行寻址这与命名管道(FIFO)是不同由于使用管道的一些限制在进程间进行通讯时管道实际上并不是一个常用的方法但是如果需要使用已有的 Linux 命令处理数据或者从 Linux 命令获得结果数据管道不失为一个好的选择匿名管道的生命周期与创建它的进程的生命周期一致当进程结束时其创建的匿名管道也将被销毁。
http://www.tj-hxxt.cn/news/232340.html

相关文章:

  • 网站空间的权限网页设计模板百度云
  • 免费商城系统网站建设莱芜大众网
  • 有了域名怎么建网站联系方式承德网站建设价格
  • 夏津网站建设wordpress 代码演示
  • 网站右侧浮动网站建设需要的设备和软件
  • thinkphp制作网站开发那些网站做任务领q币
  • 网站链接分析单位内网网站建设 开设栏目
  • 网站建设教程ppt网络舆情监测存在的问题
  • 九度网站建设wordpress 去google
  • 查询建设公司业绩网站做电子商务网站的总结
  • 厦门网站制作计划wordpress高级版
  • wordpress可以做电影网站吗策略网页游戏排行榜
  • 微信订阅号关键网站自助建站系统搭建网站
  • 网站logoico怎么做面包类网站设计
  • 平面设计师的网站凡科网小程序
  • wordpress主题demo导入东莞seo计费管理
  • 上海公司网站制作价格穿着西裤做的网站
  • 手机做任务的网站有哪些长春建设
  • 扬州网站建设外包东莞网络营销推广公司
  • 网站建设流程及费用网站建设的流程图示
  • asp.net 微网站开发教程网站的排版问题
  • 黔东南州两学一做教育网站百度优化培训
  • 从化区建设局网站做网站都需要哪些费用
  • 网站设计制作合同范本最好的wordpress 网站
  • 小型教育网站的开发与建设论文中国深圳航空公司官方网站
  • 珠海建站程序网店运营公司
  • 学校网站建设总结报告国家企业信息认证系统
  • 自动发卡网站怎么做app开发者需要更新
  • 网站设计动画广告推广免费发布
  • 建设银行积分商城网站做微信推送的网站