Linux内核-进程间通信组件的实现

码农天地 -
Linux内核-进程间通信组件的实现
Linux内核的五大组件

一个完整的Linux内核一般由五大部分组成,他们分别是内存管理,进程管理,进程间通信,虚拟文件系统和网络接口。

一、内存管理

内存管理主要完成的是如何合理有效地管理整个系统的物理内存,同时快速响应内核各个子系统对内存分配的请求。Linux内存管理支持虚拟内存,而多余出的这部分内存就是通过磁盘申请得到的,平时系统只把当前运行的程序块保留在内存中,其他程序块则保留在磁盘中。在内存紧缺时,内存管理负责在磁盘和内存间交换程序块。

二、进程管理

进程管理主要控制系统进程对CPU的访问。当需要某个进程运行时,由进程调度器根据基于优先级的调度算法启动新的进程。:Linux支持多任务运行,那么如何在一个单CPU上支持多任务呢?这个工作就是由进程调度管理来实现的。在系统运行时,每个进程都会分得一定的时间片,然后进程调度器根据时间片的不同,选择每个进程依次运行,例如当某个进程的时间片用完后,调度器会选择一个新的进程继续运行。由于切换的时间和频率都非常的快,由此用户感觉是多个程序在同时运行,而实际上,CPU在同一时间内只有一个进程在运行,这一切都是进程调度管理的结果。

进程间通信主要用于控制不同进程之间在用户空间的同步、数据共享和交换。由于不用的用户进程拥有不同的进程空间,因此进程间的通信要借助于内核的中转来实现。一般情况下,当一个进程等待硬件操作完成时,会被挂起。当硬件操作完成,进程被恢复执行,而协调这个过程的就是进程间的通信机制。

三、进程间通信

进程间通信主要用于控制不同进程之间在用户空间的同步、数据共享和交换。由于不用的用户进程拥有不同的进程空间,因此进程间的通信要借助于内核的中转来实现。一般情况下,当一个进程等待硬件操作完成时,会被挂起。当硬件操作完成,进程被恢复执行,而协调这个过程的就是进程间的通信机制。

(1)进程间通信的目的

数据传输:一个进程需要将它的数据发送给另一个进程;资源共享:多个进程之间共享同样的资源;通知事件:一个进程需要向另一个或者另一组进程发送消息,通知他们发生了某种事件。进程控制:有些进程需要完全控制另一个进程的执行,此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

(2)进程间通信方式

进程间通信主要分为两大标准:System V 标准和 POSIX 标准。
关于System V标准的进程间通信主要有以下几类:
1. 管道
管道适用于进程间的数据传输。本质上管道是操作系统在内核中为进程开辟了一块缓冲区,多个进程通过访问同一缓冲区进行通信,数据在缓冲区中以读写的方式被不同进程获取和操作。
管道有三大特性:
1.生命周期随进程;
2.自带同步与互斥;
3.提供字节流服务。
管道主要分为匿名管道和命名管道

匿名管道

前面说了管道是操作系统为进程在内核中分配的一块缓冲区,匿名管道就是指这块缓冲区没有标识符,因此其他进程无法直接访问匿名管道,只有类似父子进程这样的具有亲缘关系的进程才能使用匿名管道进行通信(原因是子进程会复制父进程的PCB,其中包括这块信息)。匿名管道的创建使用如下接口:
int pipe(int fd[2])

这个接口的作用是创建一个匿名管道,并向用户返回这个管道的操作句柄,其中参数fd[2]中fd[0]用于从管道中读取数据,fd[1]用于从管道中写入数据,如果创建成功返回 0, 创建失败返回 -1.
匿名管道的特性是:
1.若管道中没有数据,使用读操作会阻塞;

若管道写满了,使用写操作会阻塞;若管道的写端关闭了,读完数据之后不会阻塞而是返回 0;

4.若管道的读端关闭了,继续写入数据会触发异常,程序退出。
这些特性体现了管道自带同步与互斥。

命名管道

命名管道和匿名管道相反,命名管道是有标识符的一块缓冲区,并且这个标识符一般是一个可见于文件系统的文件。所以命名管道是一个特殊类型的文件,其他进程可以通过这个标识符找到这块缓冲区,即通过打开同一个管道文件,访问到同一缓冲区,进而实现进程间通信。
创建一个命名管道既可以使用mkfifo filename命令也可以使用接口,函数接口如下:
int mkfifo(const char* pathname, mode_t mode) 

参数pathname是命名管道文件的名称,mode是文件权限 。如果创建成功返回0,失败返回-1.
命名管道的打开特性:
1.若管道文件以只读的方式打开,会阻塞,直到这个文件被以写的方式打开;
2.若管道文件以只写的方式打开,会阻塞,直到这个文件被以读的方式打开;
3.若文件以读写的方式打开,就不会阻塞。
不管是匿名还是命名管道,同查那个对管道进行的数据操作的大小不超过PIPE_BUF这个宏的大小,默认是4KB。

2.共享内存
共享内存用于进程间的数据共享,是最快的进程间通信。共享内存的创建大概是以下步骤:首先,在物理内存中开辟一块空间,将这块物理内存映射到程序的虚拟地址空间,进程就可以通过虚拟地址来访问这块内存。多个进程映射到同一物理内存,这样进行通信的方式,不需要进入内核,只需要再共享的内存区进行操作即可。其他方式的通信都是因为内核中的缓冲区,进程在通信的时候会涉及内核态和用户态的两次数据拷贝。而共享内存不会所以速度更快。

共享内存的操作流程:
1.创建共享内存即开辟具有标识符的物理内存空间;
2.将共享内存映射到各个进程的虚拟地址空间;
3.直接通过虚拟地址进行对共享内存的操作;
4.解除映射;
5.释放共享内存。
int shmget(key_t key, int size, int flag)  //创建一个共享内存

void* shmat(int shmid, void* addr, int flag) //建立映射

int shmdt(void* shmstart) //解除映射

int shmctl(int shmid, int cmd, struct shmid_ds* bf) //操作共享内存
//cmd参数为IC_RMID的时候是删除共享内存 

当删除共享内存的时候,共享内存不会立即被删除(因为可能造成正在访问的进程奔溃)而是将key修改成0,表示这块共享内存不会再接收映射链接,当这块共享内存的映射链接为0的时候,则自动释放。
需要注意的是共享内存自带没有同步与互斥。
3. 消息队列
消息队列用于进程间的数据传输(有标识符)
消息队列实际上就是内核中的一个优先级队列,多个进程通过向同一个队列中 添加 或者 获取 节点来实现通信。主要是传输一个有类型(优先级)的数据块。
特性:
1.自带同步与互斥;
2.生命周期随内核;
3.数据传输自带优先级。

信号量
信号量用于实现进程间的控制,主要是同步和互斥。
本质:内核中的一个计数器(对资源进行计数) + pcb等待队列

互斥的实现:通过只有 0 / 1 的计数器,实现对临界资源访问状态的标记,在访问临界资源
之前先获取信号量,计数 -1;若计数 <0 则使进程等待(将进程pcb加入队列中);否则可
以对临界资源进行访问(并且在访问期间,已经将临界资源的状态置为不可访问状态,因此
可一保证其他进程不会再访问临界资源。当前进程访问完毕之后,则对计数进行+1,则唤醒
一个进程(将一个pcb出队,置为运行状态)

同步的实现:信号量是一个对资源的计数,可以通过计数判断是否能够获取一个资源进行处
理;若计数小于 0,则表示不能获取(并且对计数进行 -1),需要等待(加入pcb队列)。
这时候若其他进程生产一个资源,则会对计数进行 +1,若计数 <= 0 ,则唤醒一个进程。
(负数表示正在等待进程的数量,如果为正说明没有进程需要资源)。

四、虚拟文件系统

Linux内核中的虚拟文件系统用一个通用的文件模型表示了各种不同的文件系统,这个文件模型屏蔽了很多具体文件系统的差异,使Linux内核支持很多不同的文件系统,这个文件系统可以分为逻辑文件系统和设备驱动程序:逻辑文件系统指Linux所支持的文件系统,例如ext2、ext3和fat等;设备驱动程序指为每一种硬件控制器所编写的设备驱动程序模块。

五、网络接口

网络接口提供了对各种网络标准的实现和各种网络硬件的支持。网络接口一般分为网络协议和网络驱动程序。网络协议部分负责实现每一种可能的网络传输协议。网络设备驱动程序则主要负责与硬件设备进行通信,每一种可能的网络硬件设备都有相应的设备驱动程序。

本群免费分享学习资料(C/C++,Linux,golang,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,ffmpeg,TCP/IP,协程,DPDK,嵌入式)等,交流讨论领取资料请加群Q:1106675687。

特别申明:本文内容来源网络,版权归原作者所有,如有侵权请立即与我们联系(cy198701067573@163.com),我们将及时处理。

Tags 标签

加个好友,技术交流

1628738909466805.jpg