进程间通信

本文详细介绍了进程间通信的目的和实现方式,包括匿名管道和命名管道的原理、创建及使用示例,以及共享内存的概念、创建、使用注意事项和管理。重点讨论了管道的单向传输特性、读写规则,以及共享内存的高效性和管理机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1、进程间通信目的

2、如何实现进程间通信呢?

2.1、通过匿名管道

2.1.1、int pipe(int pipefd【2】)

2.1.2、如何创建管道呢?

2.1.3、模拟管道的实现的demo

2.1.4、管道读写规则

2.1.5、对管道特性的总结

2.2、通过命名管道

2.2.1、mkfifo filename

2.2.2、int mkfifo(const char *filename,mode_t mode)

3、共享内存(System V)

3.1、如何创建共享内存呢?

3.1.1、int shmget(key_t key,size_t size,int shmflg)

3.1.2、key_t ftok(const char *pathname,int proi_id) 

3.2、使用共享内存的注意事项

3.2.1、权限问题

3.2.2、关联问题(或者说叫挂接问题)(包括对shmat和shmdt函数的说明)

3.3、如何使用共享内存呢?

3.4、如何删除共享内存呢?

3.4.1、通过函数int shmctl(int shmid,int cmd,struct shmid_ds *buf)

3.4.2、通过指令ipcrm -m 加上共享内存的ID

3.4.3谁才需要删除共享内存呢?

4、临界资源


1、进程间通信目的

1.数据传输:一个进程需要将它的数据发送给另一个进程。

2.资源共享:多个进程之间共享同样的资源。

3.通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。

4.进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变
 

2、如何实现进程间通信呢?

进程间通信首先需要让不同的进程看到同一块"内存"(特定的结构组织的),那么这块内存属于哪一个进程呢?答案是不应该属于任何一个进程,而应该更强调共享。

2.1、通过匿名管道

54ed7143538f4ee485b6a7597a43fffc.png

1.如上图,父进程fork之后会创建子进程,由于进程之间具有独立性,子进程创建时会拷贝父进程的PCB信息到自己的PCB中,PCB中包含了文件描述符表,所以父进程和子进程的文件描述符表完全相同,也意味着父和子进程对在子进程被创建之前就已经打开的文件都可见,所以这也是为什么父子进程各自打印的信息可以显示在同一个显示器文件上,即显示屏上。更进一步讲,父子进程就可以通过这些文件中的某个文件当作桥梁,比如父进程向文件A写入数据,此时子进程可以从文件A读取父进程写入的数据,这样就完成了进程间的通信,而这个文件A就叫做管道文件,所以管道本质上就是内存中的一个文件,进程A将数据交给操作系统,操作系统又将数据交给进程B。

2.既然管道是一个文件,那需要将写入管道文件的数据刷新到磁盘上吗?答案是完全没必要,因为管道文件是用于进程间的通信,将数据刷新到磁盘上这个操作对通信的双方进程来说没有意义,更重要的是,如果进程之间通过管道通信需要将数据刷新到磁盘上,那双方通信就太慢了,没有效率,所以管道是个纯内存级的通信方式,管道文件里的数据也都是临时数据。

3.注意上图中只有左边的、与进程相关的内核数据结构才会在创建子进程的时候给子进程拷贝一份。而右边的、与文件相关的内核数据结构是不会在创建子进程的时候给子进程拷贝一份的。

4.管道有一个入口,一个出口,管道是单向传输资源的。如上图,假如两个进程的文件描述符表的array【3】和array【4】都指向管道文件,当情景是父进程写入数据,子进程读取数据时,就需要关闭父进程的fd为3的、打开方式为R的管道文件,然后关闭子进程的fd为4的、open打开方式为W的管道文件。

5.读了上文后可以理解管道的流程为:首先创建管道,即分别以读写方式打开同一个管道文件,然后使用 fork() 创建子进程,最后双方进程各自关闭自己不需要的文件描述符。

2.1.1、int pipe(int pipefd【2】)

94a1bbf507f04024aa88a19772ece4cb.png

f3e60b7088754a9b8fe3be89ed4c38ae.png如上图是函数基本信息

1.函数参数int pipefd【2】 是一个输出型参数,通过它可以得到被打开文件的fd,即传入参数后,函数内部会改变传入的参数。

2.返回值:调用成功时返回0,失败返回 -1并且设置错误码 errno。

2.1.2、如何创建管道呢?

efee55b922584aefb5c37857a044ef2f.png

如上图,就这么简单。首先创建一个pipe数组保存管道文件对应的2个fd,一个fd是用于读管道文件,另一个fd是用于写管道文件。然后调用pipe函数,传入之前创建的pipe数组,函数调用结束后,pipe数组就获得了管道文件的2个fd。

pipe【0】中保存的fd对应的文件一定是读端,pipe【1】中保存的fd对应的文件一定是写端,可以将0想象成嘴巴,用于读,将1想象成笔,用于写。下图中由于文件描述符的分配规则,所以输出的时候,pipefd【0】肯定是3,pipefd【1】肯定是4。

270f68a699a0415aac077b7db38357e1.png

2.1.3、模拟管道的实现的demo

下图代码中首先创建管道,然后调用fork创建子进程,然后关闭父子进程中不需要的fd(即让文件描述符表中下标为不需要的fd的元素设置为null)用于构建单向通信的信道,然后父进程向自己的buff数组中写入数据后,使用write函数将buff的数据写入管道文件中,子进程则使用read函数从管道文件中读取数据,读到的数据存入子进程的buff数组中,然后打印buff数组的内容。

注意:

代码如下

8208dd32f18140e2a5cb56ea8f6a25dc.png

运行结果如下

199bc752d52149028d150442f9bba5d5.png

2.1.4、管道读写规则

1.当没有数据可读时,如果是O_NONBLOCK disable(即如果把非阻塞模式关闭),则此时调用read会陷入阻塞,即进程暂停执行,一直等到有数据来到为止;如果是O_NONBLOCK enable(即如果把非阻塞模式打开),则此时调用read只会返回 - 1,将errno值设置为EAGAIN,并不会陷入阻塞。

2.当管道满的时候,如果是O_NONBLOCK disable(即如果把非阻塞模式关闭),则此时调用write会陷入阻塞,即进程暂停执行,直到有进程读走数据;如果是O_NONBLOCK enable(即如果把非阻塞模式打开),则此时调用write只会返回 - 1,将errno值设置为EAGAIN,并不会陷入阻塞。

3.如果所有管道写端对应的文件描述符被关闭,则read返回0。

4.如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致进程退出。为什么说是可能退出而不是一定退出呢?答案:如果没有对SIGPIPE的信号处理方式进行过设置,则默认就是杀死进程,此时进程一定会退出;但如果进行过设置,则进程就只会按照设置的逻辑去执行相关操作,如果设置的逻辑中没有杀死该进程的逻辑,此时进程就不会退出了。

5.当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。

6.当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

2.1.5、对管道特性的总结

1.管道是用来进行具有血缘关系的进程进性进程间通信-- 常用于父子通信。

2.管道可以让进程间协同工作,正因如此,管道具有访问控制的性质。这是什么意思呢?显示器也是一个文件,父子进程同时往显示器写入的时候,有没有说一个进程会等另一个进程的情况呢?并没有,所以这叫做缺乏访问控制。但管道文件不同,假如父进程是写端,子进程是读端,当父进程没有写入数据时,子进程作为读端是需要等待父进程写入的,这就叫具有访问控制。又比如说,父进程作为写端已经把管道文件写满了,此时父进程就不可以再次写入了,必须等待子进程读取数据,如果子进程一直不读,父进程就会一直等待,这也叫具有访问控制。当写端写地快,读端读地慢,写满了就不能再写了。如果写地慢,读地快,管道没有数据的时候,就必须等待。如果把写端的fd,即把pipefd【0】通过close()关闭,读端的read函数就会在读到管道文件的结尾时返回0。如果把读端的fd关闭,写端继续写入,那OS会直接终止写端的进程,因为OS知道没有人读,此时写端继续写入没有意义。

3.管道提供的是面向流式的通信服务,也被称为面向字节流。什么意思呢?即表示父进程写的次数和子进程读的次数无关,父进程可能写入100次,子进程一次就读完了,也有可能父进程作为写端写入了1次,但子进程分10次读完。流式服务需要定制协议进行数据区分,等学了网络再具体说明。

4.文件的生命周期是随进程的,比如说一个进程打开了一个文件,当这个进程退出时,打开的文件就没人使用了,OS自然会将文件释放。而管道也是文件,所以如果父子进程都退出,管道文件也会被释放掉。

5.管道通信是单向通信,是半双工通信的一种特殊情况。半双工是指通信双方的A方此时只发出信息,而另一方B方只接收信息,或者A只接收信息,B方只发出信息,不论是A还是B,他们不可以既发信息,又接收信息。而全双工就是双方都可以在发出信息的同时接收信息。

  c5f2c72c94c349d4b664eec4bc0d82b1.png

2.2、通过命名管道

1.管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。 如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。 命名管道是一种特殊类型的文件,因为进程间通过管道通信的条件是双方进程都能访问管道文件,所以命名管道和匿名管道基本相同,只不过可以在磁盘上找到命名管道,但本质上命名管道依然是内存级文件,因为数据不会刷新到磁盘上。其他性质和匿名管道没有任何区别,具体见匿名管道。

2.如何建立两个进程的通信呢?答案是两个进程都open打开同一个管道文件,open函数调用成功会返回fd,通过fd就可以找到管道文件,之后就可以进行write和read操作了。

2.2.1、mkfifo filename

 f859b98e903043dca7da4f1d4c30436b.png

如上图,命名管道可以从命令行上创建,命令行方法是使用命令:mkfifo filename。

2.2.2、int mkfifo(const char *filename,mode_t mode)

如下图,命名管道也可以从程序里创建,相关函数有:int mkfifo(const char *filename,mode_t mode)。mode表示权限,如下图的0664,0代表644是8进制数。

返回值:调用成功返回0,失败返回-1并设置错误码errno。

f9372fa146a64d61b034160074fc447d.png

说一下,如果有一个进程A打开了命名管道并想往管道中写入数据,则如果此时没有另外一个进程B打开这个命名管道,那么进程A将会被阻塞,直到有进程B open打开这个管道(不管B是想读还是想写,只要open打开了管道文件,则进程A就不会被阻塞了)。

同理,如果有一个进程A打开了命名管道并想从管道中读取数据,则如果此时没有另外一个进程B打开这个命名管道,那么进程A将会被阻塞,直到有进程B open打开这个管道(不管B是想读还是想写,只要open打开了管道文件,则进程A就不会被阻塞了)。

而匿名管道则不会有这个问题,因为匿名管道在最初调用pipe函数时,管道文件就被创建并且直接打开了,也正是因为多个进程都直接打开了匿名管道文件(在上面两段中会发生阻塞就是因为只有一个进程打开了命名管道文件,其他进程都没有打开命名管道文件),所以即使进程A往匿名管道中写入时,进程B没有调用read从管道中读取,进程A也不会阻塞,但如果进程A把管道写满了,那么进程A还是会被阻塞,直到其他进程比如进程B从匿名管道中读走一些数据后进程A才能恢复运行。

3、共享内存(System V)

1.共享内存区是最快的IPC(通信)形式,为什么呢?因为不需要调用系统接口。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。但更深一步理解,之所以共享内存是最快的IPC形式,是因为共享内存不需要频繁拷贝数据。先拿管道举例,如下图,键盘是个文件,进程A中从键盘向用户定义的缓冲区输入数据时,数据需要从键盘文件拷贝到用户缓冲区 ,这是第一次拷贝,然后将缓冲区的数据write进管道文件时,这是第二次拷贝,然后进程B从管道文件read数据到进程B的用户缓冲区中,这是第三次拷贝,然后如果调用了printf,那么将用户缓冲区的数据拷贝到显示器文件,这是第四次拷贝。再说共享内存,只需要进程A将键盘的数据拷贝进共享内存,这是第一次拷贝,此时进程B就已经可以看到共享内存中的数据了,如果进程B中调用printf,那么再将共享内存的数据拷贝进显示器文件中,这是第二次拷贝。二者相比,明显共享内存的方案拷贝次数更少,效率更优。

ceac05c037e34a378619f5cf3e450162.png

2.匿名管道和命名管道都是文件,它们可以实现进程间通信只是因为巧合,恰巧文件的性质符合了进程间通信的方案,而共享内存是专门设计的用于进程间通信的方案。

3.当有很多对进程需要进行通信并且都使用共享内存的方案时,此时就存在许多共享内存,那操作系统就需要将它们管理起来吗?和管道不同,当管道很多时,OS也需要管理管道,但恰巧管道是文件,OS管理管道的方式和管理文件的方式可以重叠,所以不需要额外设计管理管道的方案。而共享内存是OS单独设计出来的,所以答案是需要将共享内存管理起来。那怎么管理呢?通过给共享内存设计的内核数据结构管理,因为如果没有这个内核数据结构,OS就不知道该不该删除共享内存,也不知道有多少进程和共享内存有关联,不知道共享内存有多大。这些信息不会在共享内存本身的数据块中体现,它们属于共享内存的属性,所以不要以为创建共享内存就只是创建一块内存空间,这种理解是非常肤浅的,正确的理解方式为:创建共享内存=创建共享内存块(申请的内存空间)+ 创建维护共享内存的内核数据结构。所以创建比如4096字节的共享内存,实际上OS一定会开辟多于4096字节的空间,因为需要创建内核数据结构。回归正题,所以管理许多个共享内存就是将这些共享内存的内核数据结构用链表或者其他数据结构组织起来,然后通过对这个数据结构的增删查改管理共享内存。

4.共享内存是由操作系统维护的,所以共享内存不属于任何一个进程,属于OS,它是操作系统单独设立的一个模块,专门用于通信。和OS维护文件的内核数据结构不同,就算不需要进程间通信,OS也必须维护文件,但当进程间通信时不使用共享内存的方案,那OS不需要维护共享内存的内核数据结构。

5.进程间通过共享内存实现通信,本质上只是修改了页表的映射关系,将两个进程的共享内存的不同虚拟地址映射到同一个物理地址上,这样两个进程就可以通过同一块物理内存实现数据交换,从而实现通信了。

6.假如地址空间有4G,那么有3G属于用户,并且共享内存就在属于用户的3G的地址空间中,剩下的1G属于内核空间。正因为共享内存处于属于用户的3G空间中,所以不需要调用任何系统接口就可以访问共享内存,双方进程如果要通信,直接进行内存级的读写就行,而之前的pipe匿名管道和fifo命名管道,它们都需要调用系统接口read或者write进行读写。如下图画圈处,共享内存处于堆区和栈区之间的共享区,动态库也是在共享区。

51f42cda00114fffb0f67046aec5719c.png

7.共享内存缺乏访问控制,即当写端没有向共享内存写入数据甚至写端都不存在时,读端也一直会从共享内存读数据,这会造成时序问题,从而导致数据不一致。比如写端的数据还没写完(这个没写完不是指单个语句只写了一半,而是比如有5个语句,只写了1个语句),此时读端就开始读取,就会造成读端读取的数据不完整,读端拿到不完整的数据就开始使用,肯定会造成许多错误。既然共享内存缺乏访问控制,并且命名管道可以让非血缘关系的进程通信,所以可以让命名管道和共享内存协同工作,人为让共享内存具有一点访问控制。比如进程A是共享内存的读端,进程B是共享内存的写端。当进程B没有向共享内存写数据,A依然会无脑读取,为了让进程A停止读取,那就在读取前加一行代码,比如使用read函数读取命名管道内的数据,此时因为进程B没有向命名管道文件写数据,所以进程A也就卡在read函数处,进而让进程A无法读取共享内存,当进程B向共享内存写好要发送的数据后,再在进程B中向管道文件写入数据,此时进程A中read函数终于读到了命名管道中的数据,进程A也就被唤醒了,继续执行,进程A也就可以访问共享内存中的数据了。

3.1、如何创建共享内存呢?

3.1.1、int shmget(key_t key,size_t size,int shmflg)

04a3af087739423698b1af51c6ce8a92.png

1.shmget中的shm代表shared memory(共享内存)。

2.size表示共享内存的大小。size最好是页即4096字节的整数倍,因为OS申请共享内存的方案是按页的整数倍申请。比如申请4097字节,那么OS会给你申请4096×2字节的空间,但只给用户分配4097字节的空间,剩下的空间OS不会分配给用户操作 ,也不给别人使用,而是处于闲置状态,此时就造成了空间的浪费。

3.如上图,参数shmflg是两个宏,IPC_CREAT和IPC_EXCL。只传0也就是IPC_CREAT时,如果内存已经存在,则获取它并返回,如果不存在,就创建它并且返回。只传IPC_EXCL时,没有意义。同时传两个时,如果底层不存在,则创建并返回,如果已经存在,则出错返回,所以如果返回成功,则一定是创建了一个全新的shm (共享内存)。

4.调用成功则返回值是共享内存的用户层标识符,即shmid。类似文件描述符fd,之前可以使用fd操作文件,这里可以使用返回值操作共享内存。调用失败则返回-1,并设置错误码errno。

5.通过key可以让需要通信的进程A和进程B看到同一块共享内存。key的值是几不重要,但要保证key的值是唯一的,进程A和进程B的key相同,就可以保证双方使用同一块共享内存。怎样保证进程A和进程B的key相同呢?理论上需要两个进程定一个约定,比如利用同一个路径,通过同一套算法将路径转换成整形的key值,既然路径相同,经过同样的算法规则转换出来的key值肯定也相同。而实际上也确实是这么做的,比如在进程A中调用ftok函数,函数内部通过一套算法可以得到一个随机值,即key,然后进程A使用shmget函数创建出共享内存,并将key写进共享内存的内核数据结构中,然后进程B通过同样的算法规则可以得到和进程A相同的key,此时进程B调用shmget函数,拿着自己的key值在若干个共享内存的内核结构中找和自己的key值相等的,找到就匹配成功,进程A和进程B也就可以看到同一块共享内存了。那key值怎么获得呢?不需要用户输入,而是调用 ftok函数获取key值。

3.1.2、key_t ftok(const char *pathname,int proi_id) 

6844aa157a894e70a3a009586f9841f7.png

1.参数pathname也就是路径,proj_id表示项目id,一般是0到255之间,随便写也可以,因为超过范围时会被截断。

2.pathname也可以随便填,但要注意pathname这个文件用户必须具有访问权限,因为没有访问权限就获取不到文件的inode。

3.ftok函数不进行任何系统调用,函数内部只有一套算法,算法的目的是通过pathname和proj_id得到一个唯一的整形值。算法需要pathname中文件的inode,inode是个整形,然后需要proj_id,它也是整形,两个整形结合并通过一套算法就可以得到唯一的key值。

3.2、使用共享内存的注意事项

3.2.1、权限问题

1ecc6044d0664d5ba014011ad9b06532.png

如上图的perms表示权限,如果为0,即使创建了共享内存,但谁也使用不了它,也就没有意义了,所以创建时要带上权限,如下图在shmget函数的参数中添加0666选项,0表示666是个八进制数。

f52784b9a1ef4e24b5604ccc86a3c7e7.png

3.2.2、关联问题(或者说叫挂接问题)(包括对shmat和shmdt函数的说明)

1ecc6044d0664d5ba014011ad9b06532.png

如上图的nattch,n代表数量,attch表示关联(或者挂接)。共享内存被创建出后并不能直接被使用,如上图的nattch为0,此时就需要将指定的共享内存和当前进程的地址空间关联。

问题:如何关联呢?

答案:调用下图函数,即void *shmat(int shmid,const void *shmaddr,int shmflg)。

b23895e3c4064aacbe4366a0d6a496bc.png

1.shmid就是共享内存的ID。

2.参数shmaddr表示可以指定关联到虚拟地址空间上的位置。一般不推荐,因为用户一般不清楚虚拟地址的使用情况。可以将shmaddr设置成0,也就是NULL,表示让操作系统自由关联。

3.shflg表示关联时选择的方案,比如以只读方式关联。可以将它设置成0,表示以读写方式关联。

4.返回值:关联成功时返回共享内存所在的虚拟地址,该地址是共享内存的起始地址,又因为共享内存的大小是我们用户设置的,所以共享内存的结束地址我们也知道。关联失败时返回-1,并设置错误码errno。

8d741b856aad4b0f817b09089918004b.png

问题:那该如何去掉关联呢?

答案:如下图红框,使用函数 int shmdt(const void *shmaddr),dt表示delete。

8cf3ff4173f94c09ac8436ae696b66a1.png

1.只有一个参数,参数shmaddr表示共享内存的地址,将地址交给函数,就可以去掉该共享内存和当前进程的关联。如何得到这个地址呢?之前调用shmat进行关联时,关联成功会返回一个地址,所以shmat的返回值就是这个地址。

2.返回值:成功时返回0,失败时返回-1,并设置错误码errno。

97a5c200721644e689f33c0b9f109637.png

3.3、如何使用共享内存呢?

1.方法一

8f5c1dbcf5144ed092d2340755f7fb83.png

f3961f1648604e6fa5f107c4441e2cde.png前面也说过,4G内存中,共享内存在属于用户的3G虚拟地址空间里,所以不需要调用任何系统接口,直接访问内存即可,如上图2的snprintf函数,用于向一个char*的缓冲区输入数据,数据的最大字节数是size。上图1红框中的shmaddr是之前关联的共享内存的首地址,是一个void*的数据,snprintf函数会将void*隐式转换成char*,即把共享内存当作一个char*的缓冲区。在写端进程调用snprintf 函数就可以向共享内存中输入数据了。输入数据后,读端进程直接访问共享内存就可以拿到数据了,比如下图的printf(“%s”,shmaddr)。

ac911d044cde4d05976627b9b1d0f303.png
 

2.方法二b10f2b9071414378992d06c1b12c688e.png

虽然共享内存可以不需要调用系统接口,但也不是说不可以调用,如上图写端进程中的read函数,从fd为0的键盘文件中读取数据到共享内存shmaddr中,shmaddr是该进程关联的共享内存的首地址。read函数的第二个形参类型是void*,shmaddr也是void*,刚好匹配。

3.4、如何删除共享内存呢?

3.4.1、通过函数int shmctl(int shmid,int cmd,struct shmid_ds *buf)

4fdad72796944eba8ba3792e50b19e3a.png

1.函数用于删除shmget创建的共享内存,共享内存需要删除才会消失,创建共享内存的进程结束或者销毁对共享内存没有任何影响。

2.shmctl的ctl表示control(控制)。

3.shmctl很强势,即使共享内存此时已经和某个进程关联(或者说挂接),使用shmctl依旧可以将共享内存删除。

4.参数int shmid表示共享内存的 ID。

3.4.2、通过指令ipcrm -m 加上共享内存的ID

1ecc6044d0664d5ba014011ad9b06532.png

使用ipcs -m就可以查看共享内存的通信,效果图如上图,ipc表示通信,m表示memory内存。上图中的shmid就是共享内存的ID,ipcrm -m 22就可以删除该共享内存。

3.4.3谁才需要删除共享内存呢?

服务端进程需要删除,用户端进程不需要使用共享内存时,只需要取消自己进程和共享内存的关联即可,不需要删除共享内存。

4、临界资源

1.为了让进程间通信,就要让不同的进程之间看到同一份资源,之前讲的所有的通信方式,本质都是优先解决一个问题:让不同的进程看到同一份资源。

2.我们把多个进程(执行流)看到的公共的一份资源称为临界资源。

3.我们把进程访问临界资源的代码所在的区域叫临界区。

3.让不同的进程看到了同一份资源,比如共享内存,也带来了一些时序问题,造成数据不一致。

4.为了更好地进行临界区的保护,可以让多个进程在任何时刻,都只能有一个进程进入临界区,我们把这种特性叫做互斥。

5.在非临界区的多个执行流互相是不影响的。

6.每一个进程想访问临界资源中的一部分时,不能让进程直接去使用临界资源,必须先申请信号量。信号量这里可以理解成一个计数器,那么申请信号量的本质就是让信号量计数器-1。当计数器为0的时候,表示目前不可以再有其他进程访问临界资源了。当申请信号量成功,临界资源内部,一定给你预留了你想要的资源,所以申请信号量本质其实是对临界资源的一种预定机制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值