I/O及进程间通讯

三种IO设备:

    linux给程序提供三种I/O设备

        标准输入(STDIN) 0 默认接受来自键盘的输入

        标准输出(STDOUT) 1    默认输出到终端窗口

        标准错误输出流(STDERR) 2  默认输出到终端位置

I/O重定向:改变默认位置

    例如:

>:把STDOUT输出到文件

2>:把STDERR输出到文件

&>:把STDOUT和STDERR输出到文件

对Linux进程来讲,每个打开的文件都是通过文件描述符(File Descriptor)来标识的,内核为每个进程维护了一个文件描述符表,这个表以FD为索引,再进一步指向文件的详细信息。在进程创建时,内核为进程默认创建了0、1、2三个特殊的FD,这就是STDIN、STDOUT和STDERR,如下图所示意:

1.png

I/O重定向也就是让已创建的FD指向其他文件。比如,下面是对STDOUT重定向到testfile.txt前后内核文件描述符表变化的示意图

重定向前:

2.png

重定向后:

3.png

在I/O重定向的过程中,不变的是FD 0/1/2代表STDIN/STDOUT/STDERR,变化的是文件描述符表中FD 0/1/2对应的具体文件,应用程序只关心前者。本质上这和接口的原理是相通的,通过一个间接层把功能的使用者和提供者解耦


4.png

进程56989的STDOUT和进程56990的STDIN被重定向至pipe:[987113],这样就达到了两个进程标准I/O的目的

 

管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递。调用pipe系统函数即可创建一个管道。有如下特质:

1.  其本质是一个伪文件(实为内核缓冲区)

2.  由两个文件描述符引用,一个表示读端,一个表示写端。

3.  规定数据从管道的写端流入管道,从读端流出。

管道的原理: 管道实为内核使用环形队列机制,借助内核缓冲区(4k?)实现。

管道的局限性:

1.  数据自己读不能自己写。

2.  数据一旦被读走,便不在管道中存在,不可反复读取。

3.  由于管道采用半双工通信方式。因此,数据只能在一个方向上流动。

4.  只能在有公共祖先的进程间使用管道。

 

父子进程通信:

  1. 父进程调用pipe函数创建管道,得到两个文件描述符fd[0]、fd[1]指向管道的读端和写端。

  2. 父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。

3. 父进程关闭管道读端,子进程关闭管道写端。父进程可以向管道中写入数据,子进程将管道中的数据读出。由于管道是利用环形队列实现的,数据从写端流入管道,从读端流出,这样就实现了进程间通信。

 

文件描述符(file descriptor):

  文件描述符是内核为了高效管理已被打开的文件所创建的索引,其是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符。

程序刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误。如果此时去打开一个新的文件,它的文件描述符会是3。POSIX标准要求每次打开文件时(含socket)必须使用当前进程中最小可用的文件描述符号码,因此,在网络通信过程中稍不注意就有可能造成串话。

文件描述符合打开文件之间的关系

文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。

文件描述符为在该系列平台上进行设备相关的编程实际上提供了一个统一的方法

 

每一个文件描述符会与一个打开文件相对应,同时,不同的文件描述符也会指向同一个文件。相同的文件可以被不同的进程打开也可以在同一个进程中被多次打开。系统为每一个进程维护了一个文件描述符表,该表的值都是从0开始的,所以在不同的进程中你会看到相同的文件描述符,这种情况下相同文件描述符有可能指向同一个文件,也有可能指向不同的文件。具体情况要具体分析,要理解具体其概况如何,需要查看由内核维护的3个数据结构。

 

1.  进程级的文件描述符表

2.  系统级的打开文件描述符表

3.  文件系统的i-node表

进程级的描述符表的每一条目记录了单个文件描述符的相关信息。

   1. 控制文件描述符操作的一组标志。(目前,此类标志仅定义了一个,即close-on-exec标志)

   2. 对打开文件句柄的引用

 

内核对所有打开的文件的文件维护有一个系统级的描述符表格(open file description table)。有时,也称之为打开文件表(open file table),并将表格中各条目称为打开文件句柄(open file handle)。一个打开文件句柄存储了与一个打开文件相关的全部信息,如下所示:

1. 当前文件偏移量(调用read()和write()时更新,或使用lseek()直接修改)

2. 打开文件时所使用的状态标识(即,open()的flags参数)

3. 文件访问模式(如调用open()时所设置的只读模式、只写模式或读写模式)

4. 与信号驱动相关的设置

5. 对该文件i-node对象的引用

6. 文件类型(例如:常规文件、套接字或FIFO)和访问权限

7. 一个指针,指向该文件所持有的锁列表

8. 文件的各种属性,包括文件大小以及与不同类型操作相关的时间戳

 

进程间通信(IPC,Interprocess communication)

    指运行在不同进程(不论是否在同一台机器)中的若干线程间的数据交换。

    进程间通信主要包括管道, 系统IPC(包括消息队列,信号,共享存储), 套接字(SOCKET)

 

Socket

    socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,Socket也可以用“打开open  à  读写write/read  à 关闭close”模式来操作。

 

网络Socket

用于不同主机间的通信,就像聊QQ一样只要知道了对方的QQ号就可以聊天了。socket只要知道了对方的ip地址和端口就可以通信了。所以这种socket通信是基于网络协议栈的。

 

  文件Socket

    用于一台主机的进程间通信,不需要基于网络协议,主要是基于文件系统的。与Internet domain socket类似,需要知道是基于哪一个文件(相同的文件路径)来通信的。

 

    工作模型

服务端:创建socket—绑定文件(端口)—监听—接受客户端连接—接收/发送数据—…—关闭

客户端:创建socket—绑定文件(端口)—连接—发送/接收数据—…—关闭

 

管道:

    使用符号“|”来表示,用来连接命令

    如:

        命令1|命令2|命令3|

        将命令1的STDOUT发送给命令2的STDIN,命令2的STDOUT发送到命令3的STDIN

        最后一个命令会在当前shell进程的子shell进程中执行

 


猜你喜欢

转载自blog.51cto.com/11975865/2173755