Linux应用 之 管道

管道是Linux由Unix那里继承过来的进程间的通信机制

管道需要在内核和用户空间进行四次的数据拷贝:由用户空间的buf中将数据拷贝到内核中 -> 内核将数据拷贝到内存中 -> 内存到内核 -> 内核到用户空间的buf。而共享内存则只拷贝两次数据:用户空间到内存 -> 内存到用户空间。这是它底层原理的事,不是很懂。

管道用循环队列实现,连续传送数据可以不限大小。共享内存每次传递数据大小是固定的;感觉不错。管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现。

共享内存可以随机访问被映射文件的任意位置,管道只能顺序读写;emmm,有什么用呢?

共享内存区是最快的可用IPC形式,一旦这样的内存区映射到共享它的进程的地址空间,这些进程间数据的传递,就不再通过执行任何进入内核的系统调用来传递彼此的数据,节省了时间。嗯对对对,你说的对。但我只想用。

匿名管道是在具有公共祖先的进程之间进行通信的一种方式。我感觉我能用。

这里有进程生崽的机制,就是类似打开的文件资源会被共用,子进程也会获取到这个文件,如果一方只写,另一方只读,那么就会形成一个单方面的管道,这就是下面这一段话写的意思:

由父进程创建的子进程将会赋值父进程包括文件在内的一些资源。如果父进程创建子进程之前创建了一个文件,那么这个文件的描述符就会被父进程在随后所创建的子进程所共享。也就是说,父、子进程可以通过这个文件进行通信。如果通信的双方一方只能进行读操作,而另一方只能进行写操作,那么这个文件就是一个只能单方向传送消息的管道

重点来了:

进程可以通过调用函数pipe()创建一个管道。函数pipe()的原型如下:

#include <unistd.h>

int pipe(int pipefd[2]);

int pipe(int fildes[2]);

从本质上来说,pipe()函数的功能就是创建一个内存文件,它就是一个文件创造函数,类似于fopen之类的,,但与创建普通文件的函数不同,函数pipe()将在参数fildes中为进程返回这个文件的两个文件描述符fildes[0]和fildes[1]。其中,fildes[0]是一个具有“只读”属性的文件描述符,fildes[1]是一个具有“只写”属性的文件描述符,即进程通过fildes[0]只能进行文件的读操作,而通过fildes[1]只能进行文件的写操作。容易懂,就是一个二位int型数组,保存着id值,不难。成功返回0,失败返回-1;

函数调用成功返回r/w两个文件描述符。无需open,但需手动close。规定:fd[0] → r; fd[1] → w,就像0对应标准输入,1对应标准输出一样。向管道文件读写数据其实是在读写内核缓冲区。

由于这种文件没有文件名,不能被非亲进程所打开,只能用于亲属进程间的通信,所以这种没有名称的文件形成的通信管道叫做“匿名管道”。为支持匿名管道,内核初始化时由内核函数kernel_mount()安装了一种特殊的文件系统,在该系统中所创建的都是临时文件。算是八卦了。

在linux系统的某处一定会有一个表,告诉文件系统如何将物理结构转换为逻辑结构。这就涉及到i节点了。i节点是一个64字节长的表,含有有关一个文件的信息,其中有文件大小、文件所有者、文件存取许可方式,以及文件为普通文件、目录文件还是特别文件等。在i节点中最重要的一项是磁盘地址表。可以说,i节点才是文件的本体。

匿名管道文件在i节点的结构中有一个pipe_inode_info类型的指针i_pipe,在普通文件中这个指针的值为NULL,而在管道文件中这个指针则指向一个叫做管道节点信息结构的pipe_inode_info,以表明这是一个管道文件。管道实质上就是一个被当做文件来管理的内存缓冲区。

进程与其打开的管道之间的关系十分复杂:

算了,看看就算了。。。

如果父进程创建一个管道之后,又创建了一个子进程,那么由于子进程继承了父进程的文件资源,于是管道在父子进程中的连接情况就变成如下图一样的情况了:

不知道咋回事但是感觉简单多了。。。

例如在父进程中关闭文件描述符fildes[0](读文件的),在子进程中关闭文件描述符fildes[1](写文件的),这里子进程的两个符号好像是新开符,跟原来相比是新的了,这一点存疑,于是管道的连接情况就变成从子进程到父进程的单向传输管道:

通过关闭文件描述符的方法,在两个兄弟进程之间也可以实现通信管道。

管道使用read()和write()函数,采用字节流的方式,具有流动性,读数据时,每读一段数据,则管道内会清除已读走的数据。感觉还不错,但这个不是订票系统需要的。。

read:

   #include <unistd.h>
  ssize_t read(int fd, void *buf, size_t count);
  • 简单来说就是从文件描述符fd中读取count个字符给buf,返回,read就是简单的读文件的read,返回的是读取的大小n

write()跟read是一样的道理。后面的可以直接用sizrof(str),或者就是一个定值也没关系。如果顺利write()会返回实际写入的字节数。当有错误发生时则返回-1,错误代码存入errno中。

读管道时,若管道为空,则被堵塞,直至管道另一端write将数据写入到管道为止。若写段已关闭,则返回0;这个还可以理解。

写管道时,若管道已满,则被阻塞,直到管道另一端read将管道内数据取走为止。这个也能理解。

在创建管道时,写端需要关闭fildes[0]描述符,读端需要关闭fildes[1]描述符。当进程关闭前,每个进程需要将没有关闭的描述符都进行关闭。用close()函数,善后不是。

1. 如果所有指向管道写端的文件描述符都关闭了(管道写端引用计数为0),而仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到文件末尾一样。这个很容易理解,而且也没有什么关系。

 2. 如果有指向管道写端的文件描述符没关闭(管道写端引用计数大于0),而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。也就是说这时候就会卡住,哇,竟然是不同的。

3. 如果所有指向管道读端的文件描述符都关闭了(管道读端引用计数为0),这时有进程向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。当然也可以对SIGPIPE信号实施捕捉,不终止进程。会挂掉啊。。。

4. 如果有指向管道读端的文件描述符没关闭(管道读端引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再次write会阻塞,直到管道中有空位置了才写入数据并返回。感觉还是蛮不错的

由于这种管道没有其他同步措施,所以为了不产生混乱,它只能是半双工的,即数据只能向一个方向流动。如果需要双方互相传递数据,则需要建立两个管道;就是为了安全,就不要双向传递了。

只能在父子进程或兄弟进程这些具有亲缘关系的进程之间进行通信;毕竟只有它们知道文件号啊。

  • 匿名管道对于管道两端的进程而言,就是一个只存在于内存的特殊文件;一个进程向管道中写的内容被管道另一端的进程读取。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读取数据。写入读取函数就是特殊的写文件读文件函数了。

匿名管道的局限性主要有两点:一是由于管道建立在内存中,所以它的容量不可能很大;二是管道所传送的是无格式字节流,这就要求使用管道的双方实现必须对传输的数据格式进行约定。现在看来,还不是问题。

接下来说命名管道,命名管道并不常用,但是它们为进程间通讯提供了一些有趣的特性。命名管道与匿名管道除了这些区别外,其他都是一样的。

       #include <sys/types.h>
       #include <sys/stat.h>

       int mkfifo(const char *pathname, mode_t mode);

       int mknod(const char *path,mode_t mod,dev_t dev);
   //返回值:成功返回0 失败返回-1
   //参数 带环境变量的文件名(一般存在当下目录就行)  权限(一般为0666,省心)
   //dev为设备值,该值取决于⽂件创建的种类,它只在创建设备⽂件时才会⽤到。一般写0
   例:
   mkfifo("pipe",0644);

   mkfifo("/tmp/fifo", S_IFIFO|0666)

S_IFIFO|0666 指明创建一个有名管道且存取权限为0666,花里胡哨的,就这么写吧。其实没大用。
   //创建好后管道文件直接存在设定的路径 默认为当前路径
命名管道由mkfifo创建,由open打开,不像匿名管道,只要创建就打开了,而且命名管道文件可以存在任何地方。之所以说它是命名的是因为它的命名就是我们命名的,注意及时删除管道,不然如果存在管道文件的话,就会创建失败。说实在的,命名管道跟文件操作这么像了。。。果真是没什么用的。

定名管道的打开规则

 如果以后打开操纵是为读而打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
O_NONBLOCK enable:立刻返回胜利
如果以后打开操纵是为写而打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
O_NONBLOCK enable:立刻返回失败,错误码为ENXIO

    须要注意的是打开的文件描述符默认是阻塞的

匿名管道是在缓存中开辟的输出和输入文件流的空间,命名管道是一种实际存在的FIFO文件,即便是一个命名管道,它也一般是单向传递。如果要用命名管道实现两个进程间数据的双向传输,建议使用两个单向的命名管道。普通管道与命名管道的一个主要区别就是命名管道是以文件形式实实在在地存在于文件系统中的。

删除命名管道和删除一个普通文件没有什么区别:

$ rm /tmp/testp

因为使用完的命名管道文件将继续存在。因此用删除函数unlink(文件名)就可以了。

 

 

每日一个小常识:

#include<stdlib.h> perror是包含在这个文件里;

void perror(const char *s); 

perror函数只是将你输入的一些信息和现在的error所对应的错误一起输出。就是s+:它自己定义的信息,例如在读文件函数后,perror一句[sub]:success,没错,函数运行正确就会打印出success\n

标准输出文件(stdout)和标准错误输出文件(stderr),这两个文件都对应终端的屏幕。stdin对应键盘。

 

在linux系统中,文件或目录的权限可以分为3种:

r:4 读

w:2 写

x:1  执行(运行)
-:对应数值0

数字 4 、2 和 1表示读、写、执行权限

rwx = 4 + 2 + 1 = 7 (可读写运行)

rw = 4 + 2 = 6 (可读写不可运行)

rx = 4 +1 = 5 (可读可运行不可写)

示例:

最高权限777:(4+2+1) (4+2+1)  (4+2+1)

第一个7:表示当前文件的拥有者的权限,7=4+2+1 可读可写可执行权限

第二个7:表示当前文件的所属组(同组用户)权限,7=4+2+1 可读可写可执行权限

第三个7:表示当前文件的组外权限,7=4+2+1 可读可写可执行权限

存取权限为0666,即创建者、与创建者同组的用户、其他用户对该命名管道的访问权限都是可读可写,看这里多了一个0

总共分为7大列:

  第一列:文件类型,-代表普通文件 d代表目录,而在管道文件中,这个标志是p,这叫文件特殊权限。

  第二列:文件节点数(node)

  第三列:表示文件拥有者root用户

  第四列:表示文件所属组root用户组

  第五列:显示文件大小,默认是字节byte,可以通过命令 ls -lh 更人性化地查看文件大小,管道的大小永远是0,普通方法读取不出来。而且一旦读取后,管道内被读到的内容就会被删除。

  第六列:文件最后修改时间

  第七咧:文件或目录的名称

目录的默认权限为 775,文件的默认权限为 664。对比之下发现目录比文件多了执行的权限。这是因为执行权限对于目录来说是非常重要的,有了目录的执行权限才能够进入目录中进行文件操作。

默认情况下对于目录来说最大的权限是 777,对于文件来说最大的权限一般为 666(只有可以执行的文件才添加可执行权限)。所以我们创建的文件和目录的共同特点是从最大权限中减去了 2,也就是其他用户的写权限。而这个被减去的值就是我们常说的 umask。umask 显示的值为从默认的最大权限中减去的值。一共有4位,第一位 表示的是特殊权限位,一般为0。在管道编程的时候,要首先设置umask(0),别让他有什么掩码了。记住是umask,而不是unmask

Linux的open操作,感觉真是简约呢。

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

1)O_RDONLY:表示只读,也就是文件只能读,不能写入 read only,实际上是0
2)O_RDWR:表示既可以读又可以写,实际上是2
3)O_WRONLY:表示只能写操作,不能进行读操作,实际上是1

附加属性:

O_TRUNC属性去打开文件时,如果这个文件中本来是有内容的,则原来的内容会被丢弃。
O_APPEND属性去打开文件时,如果这个文件中本来是有内容的,则新写入的内容会在原来的内容后面添加

可以与上面属性用|连起来使用。它们在#include<fcntl.h>中定义。

第三个参数mode仅当创建新文件时才使用,用于指定文件的访问权限位。

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值