- 进程间通信方式
进程间通信(Inter process communication,简称IPC)指的是进程之间的信息交换,进程间通信的方式有很多,比如管道通信、信号通信、共享内存、消息队列、信号量组、POSIX信号量等。
进程间通信可以达到数据传输、共享资源、控制进程等目的,方便用户对进程进行控制和管理。
当然,Linux系统中这么多种进程间的通信方式并不是一同出现的,而是在不同时期被设计出来,Linux系统属于类Unix系统,所以Linux系统继承了很多Unix系统的设计,比如Unix系统的System-V版本中就引入了三种进程间通信方式,分别是消息队列、共享内存、信号量集。这三种通信方式也被称为System-V IPC对象。
- 共享内存概念
共享内存是Linux系统进程间通信的一种方式,是在Unix系统的system-V版本引入的一种IPC对象,除了共享内存外,其他的IPC对象还包含消息队列、信号量组。
共享内存其实就是指多个进程可以共享物理内存中的同一段内存区域,只不过还需要把物理内存映射到进程的私有虚拟地址空间中,这样多个进程就可以对同一块内存进行访问,效率非常高。
- 申请共享内存
共享内存其实是物理内存中的一段内存空间,而物理内存是由内核进行维护的,所以进程必须向操作系统申请一块物理内存,Linux系统提供了一个名称叫做shmget()的函数接口,利用该接口可以向内核申请物理内存,使用规则如下:
- 函数参数
shmget()函数的第一个参数指的是key_t类型的键值,和消息队列类似,一般用户可以使用ftok()函数生成一个唯一的键值key。
shmget()函数的第二个参数指的是要申请的共享内存段的大小,由参数size指定,size的大小可以是宏定义PAGE_SIZE的倍数。
shmget()函数的第三个参数指的是共享内存段的标志,其中IPC_CREAT指的是如果共享内存不存在则创建,IPC_EXCL指的是如果共享内存存在则表示函数调用失败。
- 返回结果
可以看到,函数调用成功,则返回创建成功的共享内存的标识符,函数调用失败,则返回-1。
- 注意事项
可以看到,申请成功的共享内存段里面存储的内容会被自动初始化为0,并且内核会为每一块创建的共享内存分配一个shmid_ds结构体来记录共享内存的属性和信息。
练习:根据函数的使用规则,在系统中创建一块共享内存,创建完成后,要求在程序中执行指令 ipc -s 来查看当前系统中IPC对象的资源情况。
- 映射共享内存
用户申请成功共享内存之后是无法直接访问的,因为缺少共享内存的入口地址,另外,由于申请的是物理内存,所以还需要把申请的物理内存映射到进程的内存空间,这样用户就可以通过共享内存入口的虚拟地址来直接访问共享内存空间。
Linux系统提供了一个名称叫做shmat()的函数接口,用户利用该接口可以实现把共享内存映射到进程的内存空间。
练习:编写2个程序,进程A(生产者)创建共享内存段,并向共享内存段中写入获取到的系统时间”2025/2/12 15:37:30”,要求进程B(消费者)从共享内存段读取出数据并输出到终端。
提示:shmget() + shmat() 2个进程都需要调用shmat()把共享内存段映射到自己的进程空间。
可以看到,Linux系统来提供了一个名称叫做shmdt()的函数接口,用户利用该接口可以解除映射。
- 控制共享内存
Linux系统内核需要对创建成功的共享内存进行维护,所以会记录每块共享内存的属性和信息,Linux系统提供了一个名称叫做shmctl()的函数接口,用户可以调用该函数实现设置共享内存的属性、获取共享内存的属性、删除共享内存等相关操作。
- 信号量的概念
通过学习IPC对象中的共享内存,可以知道共享内存是非常高效的一种通信机制,多个进程可以把相同的一段物理内存映射到各自的进程空间,并通过映射之后返回的虚拟地址就可以对这段共享进行访问。
思考:由于多个进程都会对这段共享内存进行映射,并且映射之后这些进程可能会同时对共享内存进行读写访问,所以就会导致存储在共享内存中的数据被其他进程修改,造成进程间的异常,请问应该避免这种情况发生?
回答:如果想要避免多个进程对共享资源的抢占和随意修改,就需要对各个进程进行控制和协调,达到进程间资源协同的目的。
而为了实现该目的,Linux系统提供了一种名称叫做信号量(Semaphore)的IPC资源,信号量的英文也可以翻译为信号灯,设置在马路各个路口的信号灯的作用是为了协调各个车道车辆顺序通行的。
信号量在Linux系统中的作用不是为了实现进程间信息交互,而是为了实现资源协同而设计的。
信号量本质上其实是一个数字(非负整数),用来表示可以访问资源的令牌(钥匙)的数量,当多个进程或者线程争夺这些稀缺资源的时候,可以使用信号量来保证他们合理地、秩序地使用这些资源。
思考:系统中的哪些资源可能会被多个进程之间进行抢占?既然信号量可以对进程进行协调,应该如何使用?
回答:系统中的很多资源都可能会被多个进程抢占,比如多个进程有可能同时访问的资源,比如硬件资源(外围设备、处理器等)或者软件资源(共享内存、共享变量等)。
Linux系统中一般把这些可能被进程或者线程共同访问资源称为临界资源(Critical Resources),而程序中访问这些临界资源的代码段被称为临界区(Critical Zone)。
其他的进程想要获取资源则必须提前申请信号量,使用完资源之后必须及时归还信号量(令牌 token 简单理解:钥匙!!!)。
把申请信号量的操作称为P(Passeren)操作,把归还信号量的操作称为V(vrijgeven)操作。
- P操作
P操作指的是请求分配资源,也就是说每完成一次P操作,则信号量资源会减一,当信号量资源为0时,如果有进程或者线程请求分配资源,则会导致阻塞,直到有进程释放信号量。
- V操作
V操作指的是请求释放资源,也就是说每完成一次V操作,则信号量资源会加一,使用完资源之后应该及时释放,否则其他进程申请资源时如果资源不够,则会导致阻塞。
注意:P操作和V操作属于原子操作,也就是进程在进行P操作或者V操作的过程中不会被打断,也就是不管资源申请成功还是申请失败都是一次性完成。
- 创建信号量集
对信号量进行申请和释放的前提是必须创建信号量,Linux系统中提供了一个名称叫做semget()的函数,利用该函数可以创建或者打开信号量。
- 函数参数
第一个参数指的是IPC对象的键值key,用户可以调用ftok()函数由系统生成一个key并把生成的key作为参数。
第二个参数指的是信号量集中的信号量元素数量,因为系统中的公共资源可能有多种,为了防止进程间对这些资源进行抢占,就需要为每一种资源设置一个信号量,所以信号量元素的数量可以理解为公共资源的种类。
第二个参数指的是创建或者打开信号量集的标志,用户可以选择使用标志IPC_CREAT或者IPC_EXCL,另外还可以指定信号量集的权限,权限采用八进制,比如0666,和open函数类似。
- 返回结果
函数调用成功,则会返回信号量集的标识符(一个非负整数),函数调用失败,则会返回-1。
练习:设计程序,在程序中创建一个信号量集,要求信号量集中有一个信号量,创建完成后执行指令 ipcs -a的指令,输出当前系统中IPC对象的信息。
- 操作信号量集
创建好一个信号量集之后,用户就可以对信号量集中的信号量资源进行PV操作,Linux系统提供了一个名字叫做semop()的函数,用户通过该函数可以实现对信号量进行申请或者释放。
- 函数参数
第一个参数指的是要操作的信号量集的标识符ID,该标识符可以通过semget()函数的返回值得到。
第二个参数指的是一个指向struct sembuf的结构体指针,结构体中有三个成员,如下所示:
struct sembuf
{
unsigned short sem_num;
short sem_op;
short sem_flg;
};
- sem_num成员指的是信号量集中的信号量元素的下标,通过可以访问信号量集中的某个信号量,就相当于可以访问某种公共资源。
- sem_op成员指的是对选中的信号量的操作,常见的操作就是P/V操作,如果该成员是正整数,则表示释放信号量,相当于完成V操作。如果该成员是负整数则表示申请信号量,相当于完成P操作。如果该成员的值为0时,就称为等零操作,即阻塞等待直到对应的信号量元素的值为零。
- sem_flg成员指的是对选中的信号量的操作标志,常见的标志有IPC_NOWAIT和SEM_UNDO。
第三个参数指的是要完成P/V操作的结构体数组的元素个数,利用该参数可以指定要操作多少个信号量。
注意:进行P操作时,如果信号量的值小于要申请的数量,则会导致进程阻塞,进行V操作时永远不会阻塞。
- 返回结果
函数调用成功,则返回0,函数调用失败,则返回-1,并会设置对应的错误码,用户可以根据错误码检查错误。
- 控制信号量集
Linux系统内核需要对创建成功的信号量集进行维护,所以会记录每个信号量集的属性和信息,Linux系统提供了一个名称叫做semctl()的函数接口,用户可以调用该函数接口来实现设置信号量集的属性、获取信号量集的属性、删除信号量集等操作。
练习:设计3个程序,进程A创建信号量集合共享内存,并向共享内存段中写入字符串”i am processA,my PID = xxxx”,然后进程A进入死循环。
要求进程B和进程C从进程A创建的共享内存段中读取这个字符串并输出到终端,要求进程B读取数据后才让进程C读取,利用信号量实现进程B和进程C的同步。 提示:信号量的初值是0。
- 进程间通信方式
进程间通信(Inter process communication,简称IPC)指的是进程之间的信息交换,进程间通信的方式有很多,比如管道通信、信号通信、共享内存、消息队列、信号量组、POSIX信号量等。
进程间通信可以达到数据传输、共享资源、控制进程等目的,方便用户对进程进行控制和管理。
当然,Linux系统中这么多种进程间的通信方式并不是一同出现的,而是在不同时期被设计出来,Linux系统属于类Unix系统,所以Linux系统继承了很多Unix系统的设计,比如Unix系统的System-V版本中就引入了三种进程间通信方式,分别是消息队列、共享内存、信号量集。这三种通信方式也被称为System-V IPC对象。
- 共享内存概念
共享内存是Linux系统进程间通信的一种方式,是在Unix系统的system-V版本引入的一种IPC对象,除了共享内存外,其他的IPC对象还包含消息队列、信号量组。
共享内存其实就是指多个进程可以共享物理内存中的同一段内存区域,只不过还需要把物理内存映射到进程的私有虚拟地址空间中,这样多个进程就可以对同一块内存进行访问,效率非常高。
- 申请共享内存
共享内存其实是物理内存中的一段内存空间,而物理内存是由内核进行维护的,所以进程必须向操作系统申请一块物理内存,Linux系统提供了一个名称叫做shmget()的函数接口,利用该接口可以向内核申请物理内存,使用规则如下:
- 函数参数
shmget()函数的第一个参数指的是key_t类型的键值,和消息队列类似,一般用户可以使用ftok()函数生成一个唯一的键值key。
shmget()函数的第二个参数指的是要申请的共享内存段的大小,由参数size指定,size的大小可以是宏定义PAGE_SIZE的倍数。
shmget()函数的第三个参数指的是共享内存段的标志,其中IPC_CREAT指的是如果共享内存不存在则创建,IPC_EXCL指的是如果共享内存存在则表示函数调用失败。
- 返回结果
可以看到,函数调用成功,则返回创建成功的共享内存的标识符,函数调用失败,则返回-1。
- 注意事项
可以看到,申请成功的共享内存段里面存储的内容会被自动初始化为0,并且内核会为每一块创建的共享内存分配一个shmid_ds结构体来记录共享内存的属性和信息。
练习:根据函数的使用规则,在系统中创建一块共享内存,创建完成后,要求在程序中执行指令 ipc -s 来查看当前系统中IPC对象的资源情况。
- 映射共享内存
用户申请成功共享内存之后是无法直接访问的,因为缺少共享内存的入口地址,另外,由于申请的是物理内存,所以还需要把申请的物理内存映射到进程的内存空间,这样用户就可以通过共享内存入口的虚拟地址来直接访问共享内存空间。
Linux系统提供了一个名称叫做shmat()的函数接口,用户利用该接口可以实现把共享内存映射到进程的内存空间。
练习:编写2个程序,进程A(生产者)创建共享内存段,并向共享内存段中写入获取到的系统时间”2025/2/12 15:37:30”,要求进程B(消费者)从共享内存段读取出数据并输出到终端。
提示:shmget() + shmat() 2个进程都需要调用shmat()把共享内存段映射到自己的进程空间。
可以看到,Linux系统来提供了一个名称叫做shmdt()的函数接口,用户利用该接口可以解除映射。
- 控制共享内存
Linux系统内核需要对创建成功的共享内存进行维护,所以会记录每块共享内存的属性和信息,Linux系统提供了一个名称叫做shmctl()的函数接口,用户可以调用该函数实现设置共享内存的属性、获取共享内存的属性、删除共享内存等相关操作。
- 信号量的概念
通过学习IPC对象中的共享内存,可以知道共享内存是非常高效的一种通信机制,多个进程可以把相同的一段物理内存映射到各自的进程空间,并通过映射之后返回的虚拟地址就可以对这段共享进行访问。
思考:由于多个进程都会对这段共享内存进行映射,并且映射之后这些进程可能会同时对共享内存进行读写访问,所以就会导致存储在共享内存中的数据被其他进程修改,造成进程间的异常,请问应该避免这种情况发生?
回答:如果想要避免多个进程对共享资源的抢占和随意修改,就需要对各个进程进行控制和协调,达到进程间资源协同的目的。
而为了实现该目的,Linux系统提供了一种名称叫做信号量(Semaphore)的IPC资源,信号量的英文也可以翻译为信号灯,设置在马路各个路口的信号灯的作用是为了协调各个车道车辆顺序通行的。
信号量在Linux系统中的作用不是为了实现进程间信息交互,而是为了实现资源协同而设计的。
信号量本质上其实是一个数字(非负整数),用来表示可以访问资源的令牌(钥匙)的数量,当多个进程或者线程争夺这些稀缺资源的时候,可以使用信号量来保证他们合理地、秩序地使用这些资源。
思考:系统中的哪些资源可能会被多个进程之间进行抢占?既然信号量可以对进程进行协调,应该如何使用?
回答:系统中的很多资源都可能会被多个进程抢占,比如多个进程有可能同时访问的资源,比如硬件资源(外围设备、处理器等)或者软件资源(共享内存、共享变量等)。
Linux系统中一般把这些可能被进程或者线程共同访问资源称为临界资源(Critical Resources),而程序中访问这些临界资源的代码段被称为临界区(Critical Zone)。
其他的进程想要获取资源则必须提前申请信号量,使用完资源之后必须及时归还信号量(令牌 token 简单理解:钥匙!!!)。
把申请信号量的操作称为P(Passeren)操作,把归还信号量的操作称为V(vrijgeven)操作。
- P操作
P操作指的是请求分配资源,也就是说每完成一次P操作,则信号量资源会减一,当信号量资源为0时,如果有进程或者线程请求分配资源,则会导致阻塞,直到有进程释放信号量。
- V操作
V操作指的是请求释放资源,也就是说每完成一次V操作,则信号量资源会加一,使用完资源之后应该及时释放,否则其他进程申请资源时如果资源不够,则会导致阻塞。
注意:P操作和V操作属于原子操作,也就是进程在进行P操作或者V操作的过程中不会被打断,也就是不管资源申请成功还是申请失败都是一次性完成。
- 创建信号量集
对信号量进行申请和释放的前提是必须创建信号量,Linux系统中提供了一个名称叫做semget()的函数,利用该函数可以创建或者打开信号量。
- 函数参数
第一个参数指的是IPC对象的键值key,用户可以调用ftok()函数由系统生成一个key并把生成的key作为参数。
第二个参数指的是信号量集中的信号量元素数量,因为系统中的公共资源可能有多种,为了防止进程间对这些资源进行抢占,就需要为每一种资源设置一个信号量,所以信号量元素的数量可以理解为公共资源的种类。
第二个参数指的是创建或者打开信号量集的标志,用户可以选择使用标志IPC_CREAT或者IPC_EXCL,另外还可以指定信号量集的权限,权限采用八进制,比如0666,和open函数类似。
- 返回结果
函数调用成功,则会返回信号量集的标识符(一个非负整数),函数调用失败,则会返回-1。
练习:设计程序,在程序中创建一个信号量集,要求信号量集中有一个信号量,创建完成后执行指令 ipcs -a的指令,输出当前系统中IPC对象的信息。
- 操作信号量集
创建好一个信号量集之后,用户就可以对信号量集中的信号量资源进行PV操作,Linux系统提供了一个名字叫做semop()的函数,用户通过该函数可以实现对信号量进行申请或者释放。
- 函数参数
第一个参数指的是要操作的信号量集的标识符ID,该标识符可以通过semget()函数的返回值得到。
第二个参数指的是一个指向struct sembuf的结构体指针,结构体中有三个成员,如下所示:
struct sembuf
{
unsigned short sem_num;
short sem_op;
short sem_flg;
};
- sem_num成员指的是信号量集中的信号量元素的下标,通过可以访问信号量集中的某个信号量,就相当于可以访问某种公共资源。
- sem_op成员指的是对选中的信号量的操作,常见的操作就是P/V操作,如果该成员是正整数,则表示释放信号量,相当于完成V操作。如果该成员是负整数则表示申请信号量,相当于完成P操作。如果该成员的值为0时,就称为等零操作,即阻塞等待直到对应的信号量元素的值为零。
- sem_flg成员指的是对选中的信号量的操作标志,常见的标志有IPC_NOWAIT和SEM_UNDO。
第三个参数指的是要完成P/V操作的结构体数组的元素个数,利用该参数可以指定要操作多少个信号量。
注意:进行P操作时,如果信号量的值小于要申请的数量,则会导致进程阻塞,进行V操作时永远不会阻塞。
- 返回结果
函数调用成功,则返回0,函数调用失败,则返回-1,并会设置对应的错误码,用户可以根据错误码检查错误。
- 控制信号量集
Linux系统内核需要对创建成功的信号量集进行维护,所以会记录每个信号量集的属性和信息,Linux系统提供了一个名称叫做semctl()的函数接口,用户可以调用该函数接口来实现设置信号量集的属性、获取信号量集的属性、删除信号量集等操作。
练习:设计3个程序,进程A创建信号量集合共享内存,并向共享内存段中写入字符串”i am processA,my PID = xxxx”,然后进程A进入死循环。
要求进程B和进程C从进程A创建的共享内存段中读取这个字符串并输出到终端,要求进程B读取数据后才让进程C读取,利用信号量实现进程B和进程C的同步。 提示:信号量的初值是0。