前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >unix环境高级编程(下)-高级IO和进程间通信篇

unix环境高级编程(下)-高级IO和进程间通信篇

作者头像
kinnylee
发布2020-10-15 10:10:29
1.5K0
发布2020-10-15 10:10:29
举报
文章被收录于专栏:kinnylee钻研技术

前言

笔者将《unix环境高级编程》主要内容总结为三篇:文件篇进程篇高级io和进程间通信三大板块。本文是unix环境高级编程系列文章第三篇:高级IO和进程间通信篇。该篇主要包括:

高级io

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

进程间通信

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

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

网络进程间通信

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

高级进程间通信

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

一. 高级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

  • 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 } 复制代码
  • 不同锁的兼容性:针对同一把锁。如果不同锁,新锁总是覆盖旧锁

2.3 锁的隐含继承和释放

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

2.4 FreeBSD中记录锁的数据结构

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

3. 系统v流机制

3.1 基本概念

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

3.2 STREAMS消息

  • STREAMS的所有输入输出都基于消息
  • 流首和用户进程进行消息交换的函数:read,write,ioctl,getmsg,getpmsg,putmsg,putpmsg
  • 消息可以顺流而下,也可以逆流而上
  • 消息的组成:消息类型,控制信息,数据。控制信息和数据由strbuf指定:
  • 消息约有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

  • readfds:可读描述符集,每一个文件描述符占一位
    • 内部结构视图
代码语言:txt
复制
- 描述符集的设置函数 
  • maxfdp1:最大描述符+1,可设置为FD_SETSIZE(1024)
  • writefds:可写描述符集
  • exceptfds:异常描述符集
  • tvptr:愿意等待的时间
    • NULL:永远等待,捕捉到信号则中断等待
    • 时间每个字段为0:完全不等待,测试指定的文件描述符并立即返回
    • 不为0:实际等待的时间
  • 返回值:
    • 返回-1:表示出错,文件描述符没有准备好时收到信号,此时不修改文件描述符
    • 返回0:已经超时了,指定都文件描述符都没有准备好
    • 正数:已经准备好的文件描述符数量(每个文件描述符读写单独各算一次)

4.6 pselect

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

  • 超时值的数据结构不同
  • pselect超时值为const,不可改变
  • 可使用信号屏蔽字

4.7 poll

  • poll类似与select,不过接口有所不同
  • 不是为每个状态构造文件描述符集,而是构造一个pollfd的数组,数组每个元素指定文件描述符编号和关心的状态
  • 参数:
    • events:用户设置关心的事件
    • reevents:内核返回文件描述符事件

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

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

7. readn和writen

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

8. 存储映射IO

  • 使一个磁盘空间与一个存储空间中的缓冲区映射。当从缓冲区取数据,就相当于读文件中的相应字节。写数据到缓冲区相当于自动写入文件。这样就可以不用read和write的情况下执行io
  • 文件映射到存储区:
代码语言:txt
复制
- 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 管道的创建

  • 参数fields传入两个文件描述符,field0为读而打开,field1为写而打开,field1的输出是field0的输入
  • 管道模型:

1.3 popen和pclose

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

1.4 FIFO

  • FIFO也成为命名管道,通过FIFO,不相关的进程也能交换数据
  • 创建FIFO:
  • 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结构,规定了权限和所有者。

2.3 结构限制

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

2.4 优点和缺点

缺点
  • IPC结构是在系统范围内起作用的,没有访问计数
  • IPC结构在文件系统中没有名字,不能修改属性,不能ls查看IPC对象,不能用rm删除,也不能用chmod修改权限。不能用文件描述符,也就不能使用select,poll模型
优点
  • 可靠
  • 流是受控的:缓冲区资源紧张,进程就休眠
  • 面向记录
  • 可以用非先进先出方式处理
特征对比

3. 消息队列

3.1 概述

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

3.2 数据结构

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

3.3 msgctl函数

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

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

3.4 msgsend函数

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

3.5 msgrcv函数

  • 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结构
  • 每个信号量的结构
  • 信号量的系统限制

4.3 semctl函数

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

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

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

5. 共享存储

5.1 概述

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

4.2 数据结构

  • 内核为每个共享存储段设置了shmid_ds结构
  • 共享存储的系统限制

4.3 shmctl函数

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

4.4 共享存储的使用

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

4.5 共享存储的释放

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

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

1. 套接字描述符

  • 套接字是通信端点的抽象,是用文件描述符实现的
  • 创建套接字描述符:
代码语言:txt
复制
- domain:套接字域 
代码语言:txt
复制
- type:套接字类型 
代码语言:txt
复制
- protocol:协议,通常为0。表示根据套接字类型默认选择协议关闭套接字:closeshutdown:禁止套接字上的输入/输出,可只关闭一个方向

2. 寻址

2.1 字节序

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

2.2 地址格式

  • 地址标识了套接字端点,通用地址格式为:
代码语言:javascript
复制
    struct sockaddr{
        sa_famliy_t sa_famliy;
        char        sa_data[];
    }
复制代码
  • 套接字实现可以自由添加aa_data字段以及长度
代码语言:javascript
复制
    //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套接字通用地址:,实现者可以自由添加额外字段
  • ipv6套接字通用地址:实现者可以自由添加额外字段
  • sockaddr_int和sockaddr_int6都会被转化为sockaddr结构传入套接字例程中
  • 二进制地址与文本格式地址转化:inet_ntop,inet_pton

2.3 地址查询

  • 查找给定计算机主机信息:gethostent
  • 返回的主机信息数据结构:
  • 获取网络名字和网络号
  • 获取协议名字和协议号
  • 服务名字和端口号映射关系查询
  • 将主机名和服务名映射到一个地址
  • 地址信息包含的成员

2.4 将套接字与地址绑定

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

3. 建立连接

3.1 connect

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

3.2 listen

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

3.3 accept

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

4. 数据传输

4.1 send

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

4.2 recv

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

5. 套接字选项

5.1 套接字选项包括

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

5.2 设置套接字的函数

6. 带外数据

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

四. 高级进程间通信

1. 概述

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

2. STREAMS管道

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

3. UNIX域套接字

  • 用于在同一台机器上运行的进程之间通讯
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
    • 高级io
      • 进程间通信
        • 网络进程间通信
          • 高级进程间通信
          • 一. 高级IO
            • 1. 非阻塞IO
              • 1.1 概念
              • 1.2 如何指定非阻塞io
            • 2. 记录锁
              • 2.1 概述
              • 2.2 fcntl
              • 2.3 锁的隐含继承和释放
              • 2.4 FreeBSD中记录锁的数据结构
            • 3. 系统v流机制
              • 3.1 基本概念
              • 3.2 STREAMS消息
              • 3.3 putmsg和putpmsg
              • 3.4 getmsg和getpmsg
            • 4. IO多路转接
              • 4.1 阻塞io
              • 4.2 非阻塞io
              • 4.3 异步io
              • 4.4 IO多路转接
              • 4.5 select
              • 4.6 pselect
              • 4.7 poll
            • 5. 异步IO
              • 5.1 概述
              • 5.2 SystemV异步IO
              • 5.3 BSD异步IO
            • 6. readv和writev
              • 7. readn和writen
                • 8. 存储映射IO
                • 二. 进程间通信
                  • 1. 管道
                    • 1.1 概述
                    • 1.2 管道的创建
                    • 1.3 popen和pclose
                    • 1.4 FIFO
                  • 2. XSI IPC
                    • 2.1 标识符和键
                    • 2.2 权限结构
                    • 2.3 结构限制
                    • 2.4 优点和缺点
                  • 3. 消息队列
                    • 3.1 概述
                    • 3.2 数据结构
                    • 3.3 msgctl函数
                    • 3.4 msgsend函数
                    • 3.5 msgrcv函数
                  • 4. 信号量
                    • 4.1 概述
                    • 4.2 数据结构
                    • 4.3 semctl函数
                    • 4.4 信号量与记录锁在liunx的对比
                  • 5. 共享存储
                    • 5.1 概述
                    • 4.2 数据结构
                    • 4.3 shmctl函数
                    • 4.4 共享存储的使用
                    • 4.5 共享存储的释放
                • 三. 网络进程间通信:套接字
                  • 1. 套接字描述符
                    • 2. 寻址
                      • 2.1 字节序
                      • 2.2 地址格式
                      • 2.3 地址查询
                      • 2.4 将套接字与地址绑定
                    • 3. 建立连接
                      • 3.1 connect
                      • 3.2 listen
                      • 3.3 accept
                    • 4. 数据传输
                      • 4.1 send
                      • 4.2 recv
                    • 5. 套接字选项
                      • 5.1 套接字选项包括
                      • 5.2 设置套接字的函数
                    • 6. 带外数据
                    • 四. 高级进程间通信
                      • 1. 概述
                        • 2. STREAMS管道
                          • 3. UNIX域套接字
                          相关产品与服务
                          对象存储
                          对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
                          领券
                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档