linux高级编程_linux环境高级编程-高级IO和进程间通信详解

高级io

先介绍记录锁的概念和记录锁的数据结构。然后介绍阻塞io,非阻塞IO,异步io,IO多路转接等概念,后者都是针对前者更优的技术。IO多路转接技术包括:select,peslect,poll。最后介绍存储映射IO。

进程间通信

介绍了基本进程间通信机制,包括两大类:

  • 进程间数据共享:管道,FIFO,消息队列和共享存储
  • 进程间数据同步:信号量

网络进程间通信

介绍网络间的进程通信机制:套接字。首先是如何寻址。然后介绍socket编程的连接建立,数据传输等。

高级进程间通信

高级进程间通信提供一种可以在进程间传递文件描述符的机制,包括STREAMS管道和unix域套接字

需要C/C++ Linux服务器架构师学习资料后台私信“资料”(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

a671b1d6dfe00928957c7a9822f020be.png

一. 高级IO

1. 非阻塞IO

1.1 概念

  • 非阻塞io使得与磁盘io有关的系统调用永远不会被阻塞
  • 这些io相关的系统调用有:open,read,write
  • 如果这种操作不能完成,则调用立即出错返回

1.2 如何指定非阻塞io

  • 如果调用open获得文件描述符,可指定O_NONBLOCK标识
  • 对于已经打开的文件描述符,可调用fcntl,由该函数打开O_NONBLOCK标识

2. 记录锁

2.1 概述

  • 概念:当一个进程正在读或修改文件的某个部分时,可以阻止其他进程修改同一文件区
  • flock:文件锁,早期的unix只支持锁整个文件,使用该函数
  • fcntl:记录锁,允许锁文件中的任意字节数的区域

2.2 fcntl

937367e4aa2dfaf5b1c89c65de8004fe.png

cmd:

F_GETLK:获取锁信息

F_SETLK:设置锁信息

F_SETLKW:阻塞版本的F_SETLK

flockptr:指向flock的指针struct flock{ short l_type;//F_RDLCK共享读锁,F_WRLCK独占写锁,F_UNLCK解锁 off_t l_start;//加锁区域其实位置 short l_whence;//和start一起确定加锁位置 off_t l_len;//加锁长度 pid_t l_pid;//进程id }

不同锁的兼容性:针对同一把锁。如果不同锁,新锁总是覆盖旧锁

1fc0be81d790188ee9096238d16e7b06.png

2.3 锁的隐含继承和释放

  • 进程终止时,所建立的锁全部释放
  • 关闭文件描述符时,文件描述符引用的文件上的任何一把锁都被释放
  • fork产生的子进程不继承父类设置的锁
  • 执行exec后,新进程可以继承原程序的锁

2.4 FreeBSD中记录锁的数据结构

af2318ef7e626e35af3f1dd6c3392207.png
  • v节点表的i节点结构串联起所有的lockf结构
  • 每个lockf结构说明了一个给定进程的一个加锁区域
  • 在父进程中,关闭任意一个文件描述符,内核都会遍历i节点各项lockf,并释放持有的锁

3. 系统v流机制

3.1 基本概念

  • STREAMS是系统V提供的构造内核设备驱动程序和网络协议包的一种通用方法。不同于标准io中的stream
  • 流在用户进程和设备驱动程序之间提供一条全双工通路,流无需和实际硬件设备之间会话
  • 简单流的基本结构:
174c8ea36e64e625ec1bc37ae7248953.png

3.2 STREAMS消息

  • STREAMS的所有输入输出都基于消息
  • 流首和用户进程进行消息交换的函数:read,write,ioctl,getmsg,getpmsg,putmsg,putpmsg
  • 消息可以顺流而下,也可以逆流而上
  • 消息的组成:消息类型,控制信息,数据。控制信息和数据由strbuf指定:
659d9f30b1279634655364d775cd17f4.png
  • 消息约有25种,但一般使用的只涉及三种: M_DATA:用户数据M_PROTO:协议控制信息M_PCPROTO:高优先级协议控制信息
  • 每个输入STREAMS模块有两个输入队列,一个来自上面模块的消息,另一个来自下面模块的消息
  • 流中的消息都有一个排队优先级,通过优先级波段指定

3.3 putmsg和putpmsg

  • 用于将STREAMS消息写入流中
  • 后者允许指定优先级波段

3.4 getmsg和getpmsg

  • 从流首读STREAMS消息

4. IO多路转接

4.1 阻塞io

  • 读取一个文件描述符对数据,如果没有数据就一直阻塞住
  • 缺点:长时间阻塞在同一个文件描述符,另一个文件描述符虽然有很多数据却得不到及时处理

4.2 非阻塞io

  • 将两个文件描述符都设置为非阻塞的
  • 对第一个文件描述符发送read,如果该输入上有数据,则读取并处理。如无数据则立即返回。
  • 第二个描述符重复上一步操作
  • 若干秒后,重复执行以上步骤,即轮询
  • 缺点:浪费cpu时间,大多数时间实际上上无数据可读的。轮询的时间间隔也很难确定

4.3 异步io

  • 当一个文件描述符已准备好可以进行io时,用一个信号通知它
  • 缺点:并发所有的系统都支持,其次这种信号对每个进程而言只有一个

4.4 IO多路转接

  • 一种比异步IO更好的处理IO的技术
  • 先构造一张有关描述符的图表,然后调用一个函数,直到这些描述符中至少一个准备好io时,该函数才返回。返回时,告诉哪些文件描述符已准备好可以io
  • 支持IO多路转接的函数:poll,pselect,select

4.5 select

d1b9f3a81b51fed1945a831b6acab991.png

readfds:可读描述符集,每一个文件描述符占一位

内部结构视图

c5324596602a6740824436430ba1ac41.png

描述符集的设置函数

670a9265faaece7ce9de3101b2241479.png

maxfdp1:最大描述符+1,可设置为FD_SETSIZE(1024)

writefds:可写描述符集

exceptfds:异常描述符集

tvptr:愿意等待的时间 NULL:永远等待,捕捉到信号则中断等待时间每个字段为0:完全不等待,测试指定的文件描述符并立即返回不为0:实际等待的时间

返回值: 返回-1:表示出错,文件描述符没有准备好时收到信号,此时不修改文件描述符返回0:已经超时了,指定都文件描述符都没有准备好正数:已经准备好的文件描述符数量(每个文件描述符读写单独各算一次)

4.6 pselect

pselect与select类似,仅仅少部分有差异,如下:

超时值的数据结构不同

pselect超时值为const,不可改变

可使用信号屏蔽字

42c63f5bec047f8bc9d31caf63ee20c7.png

4.7 poll

365c4f16c7d460aa5846c331ac68951b.png

poll类似与select,不过接口有所不同

不是为每个状态构造文件描述符集,而是构造一个pollfd的数组,数组每个元素指定文件描述符编号和关心的状态

543b8743333a7d4278ca5fa545b1ab0b.png

参数: events:用户设置关心的事件 reevents:内核返回文件描述符事件

8b9d78cc79d8e1c849b3fb64a180ba50.png

5. 异步IO

5.1 概述

异步io并不像select和poll对所有文件描述符都生效

  • SystemV系统:只对STREAMS设备和STREAMS管道起作用,发送SIGPOLL信号
  • BSD系统:只对终端和网络起作用,发送SIGIO信号

5.2 SystemV异步IO

  • 启动异步IO,需要调用ioctl,第二个参数为I_SETSIG
  • 同时,在调用ioctl之前建立信号处理程序

5.3 BSD异步IO

异步IO是SIGIO(通用异步io)和SIGURG(通知网络进程数据到达)两个信号的组合

  • 调用signal或signalaction为SIGIO建立信号处理程序
  • 以命令F_SETOWN调用fcntl设置进程id和进程组id,将接收对于该描述符的信号
  • 以命令F_SETFL调用fcntl设置O_ASYNC文件状态标识,使文件描述符上可以进行异步IO

6. readv和writev

  • 用于在一次函数调用中读写多个非连续的缓冲区
a5efbe7570093a615eae60dd2a7b15e5.png

7. readn和writen

  • 按需多此调用read和write,直至读写了N各字节数据
  • 使用与读写管道,网络设备或终端数据
71e8ffaebb4ea06cceac987271fd6292.png

8. 存储映射IO

  • 使一个磁盘空间与一个存储空间中的缓冲区映射。当从缓冲区取数据,就相当于读文件中的相应字节。写数据到缓冲区相当于自动写入文件。这样就可以不用read和write的情况下执行io
  • 文件映射到存储区:
2da3ce14946630f1cbcd14a161bea461.png
  • addr:存储映射起始地址,通常设置为0,表示由系统选择地址然后作为返回值返回
  • port:说明对存储映射区的保护要求,权限不能超过文件本身权限 PORT_READ:映射区可读PORT_WRITE:映射区可写PORT_EXEC:映射区可执行PORT_NONE :映射区不可访问
  • flag: MAP_FIXED:返回值必须等于addr,不利于移值MAP_SHARED:存储操作的配置MAP_PRIVATE:创建私有副本
  • 更改存储映射区权限:mprotect
  • 刷新映射存储区:msync
  • 解除存储映射区:munmap

二. 进程间通信

进程间通信机制包括:

  • 经典IPC:管道,FIFO,消息队列,信号量,共享存储
  • 网络IPC:套接字

1. 管道

1.1 概述

  • 最古老的ipc机制
  • 管道有两个局限性: 历史上,它是半双工的,即数据只能在一个方向流动。虽然现在某些系统提供全双工,但是为了移植性,不假定它有此特性他们只能在具有公共祖先的进程之间使用
  • 尽管有局限性,半双工管道仍然是最常用的ipc
  • 若write写一个尚无进程为读而打开的管道,产生SIGPIPE信号
  • 若管道的最后一个写进程关闭该管道,则为管道的读进程产生文件结束标识

1.2 管道的创建

0b6c58c9769ba93f9b67e1fb406b7413.png
  • 参数fields传入两个文件描述符,field[0]为读而打开,field[1]为写而打开,field[1]的输出是field[0]的输入
  • 管道模型:
d2d71cf903e230fe65c4d8c593936d0a.png

1.3 popen和pclose

ff84a05015540964b45f6a7bf421d5a7.png
  • popen先执行fork,然后调用exec以执行cmdstring,并返回标准io文件指针。如果type=“r“,文件指针连接到cmdstring的标准输出。如果type=“w”,文件指针连接到cmdstring的标准输入
  • pclose关闭标准io流

1.4 FIFO

  • FIFO也成为命名管道,通过FIFO,不相关的进程也能交换数据
  • 创建FIFO:
6ef25bd71a137f0614ef1757f2ff3db6.png
  • mode参数与open函数一致
  • 非阻塞标准O_NONBLOCK: 没有指定该参数:只读open要阻塞到某个其他进程为写而打开此FIFO指定该参数:只读open立即返回。没有进程打开FIFO,将出错返回-1
  • 类似与管道,若write写一个尚无进程为读而打开的FIFO,产生SIGPIPE信号。若FIFO的最后一个写进程关闭该FIFO,则为FIFO的读进程产生文件结束标识
  • PIPE_BUF说明了可被原子写到FIFO的最大数据量
  • FIFO的用途 由shell命令使用,以便将数据从一条管道线传到另一条,无需创建中间临时文件用于客户-服务器进程中,以在客户进程和服务器进程间传递数据

2. XSI IPC

消息队列,信号量和共享存储,这三种IPC称做XSI IPC,他们之间有很多共性,包括:

2.1 标识符和键

  • 标识符:唯一标识IPC对象的内部名,非负整数
  • 键:IPC对象的外部名,使多个合作进程能在同一个IPC对象上会合。键基本数据类型为key_t
  • 客户进程和服务器进程在同一IPC上会合的方法: 服务器进程指定键IPC_PRIVATE创建一个新的IPC结构,将返回的标识符放到某处(文件)给客户进程使用。缺点:要分别读写文件在公共头文件中定义一个键,服务器进程指定该键创建IPC结构。缺点:可能IPC已经存在,获取时会出错客户进程和服务器进程认同一个路径名和项目id,接着调用ftok将两个值变换为键,再调用方法2

2.2 权限结构

  • XSI IPC为每个IPC结构设置了一个ipc_perm结构,规定了权限和所有者。
47c60453a23d35bbf6a627733a757ef0.png

2.3 结构限制

  • 三种形式的IPC都有内置限制

2.4 优点和缺点

缺点

  • IPC结构是在系统范围内起作用的,没有访问计数
  • IPC结构在文件系统中没有名字,不能修改属性,不能ls查看IPC对象,不能用rm删除,也不能用chmod修改权限。不能用文件描述符,也就不能使用select,poll模型

优点

  • 可靠
  • 流是受控的:缓冲区资源紧张,进程就休眠
  • 面向记录
  • 可以用非先进先出方式处理

特征对比

a57b2faee4d9876c8f9ff67615d00d17.png

3. 消息队列

3.1 概述

  • 消息的链接表,存放在内核中,由消息队列标识符标识
  • 最开始出现的为了提供比一般IPC更高速度的通讯方式,但现在速度上没有优势,已经不再使用了
  • 创建或打开队列:msgget
  • 发送消息:msgsend
  • 获取消息:msgrcv,不一定先进先出,可按消息的类型字段取

3.2 数据结构

  • 每个队列相关的数据结构
25b98bd4c26faa436c10cd4d6ac8e17f.png
  • 消息队列在各个系统中的参数限制
cb4ae4c255c9a117b401d3ff64a4b2ed.png

3.3 msgctl函数

msgctl函数对队列执行多装操作(类似于ioctl,垃圾桶函数)

28a1a07b144c63353c6c85992950751a.png
  • cmd:要执行的命令 IPC_STATE:获取msgid_ds结构,并放入buf参数IPC_SET:按buf值,设置数据IPC_RMID:删除队列和数据

3.4 msgsend函数

fb83ab4702bbc80e353e568be8519033.png
  • ptr:指向消息内容指针,消息的组成: 类型:正长整型类型长度实际数据
  • flag:标志 IPC_NOWAIT:非阻塞io

3.5 msgrcv函数

6bdb1cc2cdb6f8c051fc212015ec68b9.png
  • ptr:获取的数据地址,包括类型和实际数据
  • nbytes:数据缓冲区长度
  • type:获取哪种消息。 type=0:返回队列中第一条消息type>0:返回消息类型为type的第一个消息type<0:返回消息类型小于等于type绝对值的消息
  • flag: IPC_NOWAIT:非阻塞

4. 信号量

4.1 概述

  • 信号量不同于管道和消息队列,它是一个计数器,用于多进程堆共享数据对象的访问
  • 信号量计数操作必须是原子的,通常在内核中实现
  • 使用信号量获取共享资源的操作 测试该资源的信号量N若N为正,则进程可以使用该资源。然后N=N-1,表示使用了一个资源单位若N=0,则进程休眠,直到N>0才唤醒,然后第一步当进程不使用共享资源时,N=N+1,如果有进程在休眠等待则唤醒
  • XSI信号量相对复杂一些 信号量并发单个非负值,而是一个或多个信号量值的集合创建信号量和赋值是分开的,不能原子的创建信号集合即使没有进程在使用信号量,他仍然存在
  • 获得一个信号量ID:semget

4.2 数据结构

  • 内核为每个信号量集合设置了一个semid_ds结构
b9be76e50e4c6d606a9dc977bba75dc4.png
  • 每个信号量的结构
bf31bc690df0d0a4f68815dcec1b7f17.png
  • 信号量的系统限制
faa3ef4b37878ff7d353d9e6bcbc900e.png

4.3 semctl函数

  • 包含多种信号量操作
7f43f22171716695832fd221f8ea819e.png
  • cmd: IPC_STAT:取semid_ds结构IPC_SET:设置数据IPC_RMID:删除信号量集合

4.4 信号量与记录锁在liunx的对比

  • 记录锁比信号量耗时
  • 但如果只锁一个资源,宁可用记录锁。因为他使用简单,进程终止时会自动清理锁

5. 共享存储

5.1 概述

  • 共享存储允许两个或更多进程共享给定的存储区
  • 数据不需要在进程间复制,是最快的IPC
  • 多进程对于同一个存储区,要注意同步访问,通常使用信号量来进行同步
  • 获取共享存储区域id:shmget
  • 共享存储的位置:栈下面
11bf3018fd7faf046e508d777eb403f3.png

5.2 数据结构

  • 内核为每个共享存储段设置了shmid_ds结构
2ffbafda0f0ba1a67daf8d7ba8a67d0f.png
  • 共享存储的系统限制
645f987bddf3cbf561719eeb39d4d327.png

5.3 shmctl函数

  • 包含堆共享存储的多种操作
04356ce78e3633b10294a01fa9b64a4d.png
  • 参数同前面

5.4 共享存储的使用

  • shmat函数:进程用于连接共享存储到其他的地址空间中
27767d232d4e254321ba2cc40a6b7b8c.png
  • addr参数: 为0:连接到由内核选择的可以地址上,推荐方式非0:且没有指定SHM_RND,连接到该地址非0:指定SHM_RND,将地址向下取最低边界地址倍数
  • flag: SHM_RDONLY:只读其他:读写

5.5 共享存储的释放

  • shmdt:脱离该段,但并不删除数据,标识符还在,直到调用shmctl删除

三. 网络进程间通信:套接字

1. 套接字描述符

  • 套接字是通信端点的抽象,是用文件描述符实现的
  • 创建套接字描述符:
653e89f5c129460b53a9698d2d7dd054.png
  • domain:套接字域
82e3c9a17b360c0b2c1a49e87832ab75.png
  • type:套接字类型
74c413743d67308f8761614544251a1a.png
  • protocol:协议,通常为0。表示根据套接字类型默认选择协议
  • 关闭套接字:close
  • shutdown:禁止套接字上的输入/输出,可只关闭一个方向

2. 寻址

2.1 字节序

  • 大端字节序:最大字节地址对应于数字最低有效字节
  • 小段字节需:最小字节地址对应于数字最低有效字节
  • 各个平台的字节序如下:
b08b555805afa583fa37d4b2ff2ad443.png
  • 网络传输中:tcp/ip使用大端字节序

2.2 地址格式

  • 地址标识了套接字端点,通用地址格式为:
    struct sockaddr{        sa_famliy_t sa_famliy;        char        sa_data[];    }
  • 套接字实现可以自由添加aa_data字段以及长度
    //linux实现    struct sockaddr{        sa_famliy_t sa_famliy;        char        sa_data[14];    }    //freeBSD实现     struct sockaddr{        unsigned char sa_len;        sa_famliy_t sa_famliy;        char        sa_data[14];    }
  • ipv4套接字通用地址:,实现者可以自由添加额外字段
d9fd01e0007bcd6a2ef350c8becc9e87.png
  • ipv6套接字通用地址:实现者可以自由添加额外字段
f18f11d38580b9d63afdad14ae68abe2.png
  • sockaddr_int和sockaddr_int6都会被转化为sockaddr结构传入套接字例程中
  • 二进制地址与文本格式地址转化:inet_ntop,inet_pton

2.3 地址查询

  • 查找给定计算机主机信息:gethostent
4db3c85ae279ab4734df1d6d6d871d36.png
  • 返回的主机信息数据结构:
113d3e18404fae52e72cc8035f2b63c9.png
  • 获取网络名字和网络号
dfde99c44a8f9fa8da55a0510974dc21.png
  • 获取协议名字和协议号
00b1bb818971b40f06a6ebc6c043144d.png
  • 服务名字和端口号映射关系查询
9d1241cf10055bc010802dfd19828f81.png
  • 将主机名和服务名映射到一个地址
5820f96cbe6a9027f52c0464652090e6.png
  • 地址信息包含的成员
d3e3ba08e2a70f431f86543ee087210e.png

2.4 将套接字与地址绑定

  • 客户端套接字关联地址没有太大意义,可以让系统选一个默认地址
  • 服务端需要给一个客户端请求的套接字绑定一个众所周知的地址
  • 客户端绑定服务端地址的方法:
d89de48d7cd8d672b3a30c58c7a0698c.png

3. 建立连接

3.1 connect

1b5468feb445296bc303e304017180c3.png
  • connect为客户端调用,用于连接请求
  • addr为服务器地址
  • 如果sockfd没有绑定地址,connect会给调用者绑定一个默认地址
  • 连接可能失败,应用程序必须能处理connect返回的错误

3.2 listen

6d2e5215fec334934ed64a06475e259e.png
  • listen为服务端调用
  • 服务器用listen宣告可以接受连接请求
  • backlog:连接请求数量

3.3 accept

7155db27bfdf59a1627e35a777dddece.png
  • accept获得连接请求,并建立连接
  • 返回的文件描述符是套接字描述符,描述符连接到调用connect到客户端
  • 新的套接字描述符和原始套接字sockfd具有相同的套接字类型和地址族
  • 传给accept的原始套接字没有关联到这个连接,而是继续保存可以状态并接受其他连接请求
  • 如果没有连接请求等待处理,accept会阻塞直到有请求到来

4. 数据传输

4.1 send

cd459e2cec8eaf0bcf63a9048f78a5f3.png
  • 发送数据,类似与write函数
  • send比write多了第四个参数flags,用于改变处理数据到传输方式 MSG_DONTROUTE:勿将数据路由出本地网络MSG_DONTWAIT:允许非阻塞操作MSG_EOR:记录结束MSG_OOB:外带数据
  • sendto函数:类似send。但是sendto允许在勿连接到套接字上指定一个目标地址

4.2 recv

faace6e90cf8b14b28a34b7c84af40b5.png
  • 获取数据,类似于read函数
  • recv比read多了第四个产生flags,用于控制如何接收数据 MSG_OOB:接受外带数据MSG_PEEK:返回报文内容而不真正取走报文MSG_TRUNC:即使报文被截短,也返回实际的长度MSG_WAITALL:等待直到所以数据可用

5. 套接字选项

5.1 套接字选项包括

  • 通用选项,工作在所有套接字类型上
  • 在套接字层次管理的选项,但是依赖底层协议的支持
  • 特定与某种协议的选项,为某个协议独有

5.2 设置套接字的函数

54db26a9fc8c5b238862f2ae3ddfebfd.png

6. 带外数据

  • 带外数据是一些通信协议支持的可选特征,允许高优先级的数据比普通数据优先传输
  • TCP将外带数据成为“紧急数据”

四. 高级进程间通信

1. 概述

  • Streams管道和unix套接字,这两种高级IPC,可以在进程间传递文件描述符
  • 服务进程可以使他们的打开文件描述符与特定的名字相关联
  • 客户进程可以使用这些名字与服务器通信
  • 操作系统会为每个客户进程提供一个独自的IPC通道

2. STREAMS管道

  • Streams pipe是一个全双工(双向)通道
  • 内部结构如下
598b50231bc836077a1a7ac7b023974a.png

3. UNIX域套接字

  • 用于在同一台机器上运行的进程之间通讯
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值