进程间通信和同步

进程间通信和同步

在linux下有过种进程间通信的方法:半双工管道,FIFO(命名管道),消息队列,信号量,共享内存,socket等

半双工管道


管道式Linux系统中最古老的进程间通信机制,这里所说的管道是指无名管道(PIPE),它可用于具有亲缘关系进程间的通信.有名管道(FIFO)克服了管道没有名字的限制,因此,除了具有管道所有具有的功能外,它还允许无亲缘关系进程间的通信.Linux的管道主要包括两种:无名管道和有名管道,本文主要介绍这两种管道.

把一个进程连接到另一个进程的一个数据流称为"管道".比如:ls|more.这条命令的作用是列出当前目录下的所有文件盒子目录,如果内容超过了一页则自动进行分页.符号"|"就是shell为ls和more命令简历的一条管道,它将ls的输出直接送进了more的输入.

无名管道特点

  • 只能用于具有亲缘关系的进程之间的通信(也就是父子进程或者兄弟进程之间).
  • 它是一个半双工的通信模式,具有固定的读端和写端.
  • 管道也可以看成是一种特殊的文件,对于它的读写也可以使用普通的read(),write()等函数.但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中.
下面的代码时演示如何利用两个管道在父进程和子进程之间进行通信。在父进程和子进程之间建立一个管道,子进程向管道中写入数据,父进程从管道中读取数据,要实现这样的模型,在父进程中需要将管道的写段关闭,在子进程中需要将读端关闭。下面看代码:
     
     
     
     
  1. #include
  2. #include
  3. #include
  4. #include
  5. #include
  6. int main(void)
  7. {
  8. int result = -1;
  9. int fd[2],nbytes;
  10. pid_t pid;
  11. char string[] = "1111111111111111";
  12. char readbuffer[40];
  13. memset(readbuffer,0,40);
  14. int *write_fd = &fd[1];
  15. int *read_fd = &fd[0];
  16. //建立管道
  17. result = pipe(fd);
  18. if(result == -1)
  19. {
  20. //返回-1失败
  21. printf("建立管道失败\n");
  22. return -2;
  23. }
  24. pid = fork();//分叉进程,返回两次:0表示子进程;>0表示父进程
  25. if(pid == -1)
  26. {
  27. //返回-1表示分叉进程失败
  28. printf("分叉进程失败\n");
  29. return -1;
  30. }
  31. if(pid == 0)
  32. {
  33. //0表示子进程
  34. close(*read_fd);//关闭管道读那端
  35. //写数据
  36. write(*write_fd,string,strlen(string));
  37. return 0;
  38. }
  39. else
  40. {
  41. //表示父进程
  42. close(*write_fd);//关闭写端
  43. nbytes = read(*read_fd,readbuffer,sizeof(readbuffer));
  44. printf("读取字节总数:%d %s\n",nbytes,readbuffer);
  45. }
  46. return 0;
  47. }
需要注意的是:逛到的容量十分有限,在头文件中定义的常量PIPE_BUF规定的管道一次传送的最大字节数(4096).

在上面的例子中,我们建立一条从父进程到子进程的管道,需要在父进程中关闭管道的读端(pipe[0]),在子进程中关闭管道的写段(pipe[1]),如下图所示:


管道读写注意点:
  • 只有在管道的读端存在时,向管道写入数据才有意义.否则,向管道写入数据的进程将收到内核传来的SIGPIPE信号(通常为Broken pipe错误).
  • 向管道写入数据时,Linux将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据.如果读进程不读取管道缓冲区中的数据,那么写操作将会一直阻塞.
  • 父子进程在运行时,它们的先后次序并不能保证,因此,在为了保证父子进程已经关闭了相应的文件描述符,可在两个进程中调用sleep()函数,当然这种调用不是很好的解决方法,在后面学到进程之间的同步与互斥机制之后.
管道操作原子性

下面的代码为一个管道读写的例子。在成功建立管道后,子进程向管道中写入数据,父进程从管道中读取数据,子进程一次写入128K个字节的数据,父进程每次读取10K字节的数据。当父进程没有数据可读时推出。
     
     
     
     
  1. #include
  2. #include
  3. #include
  4. #include
  5. #include
  6. #define K 1024
  7. #define WRITELEN (128*K)
  8. int main(void)
  9. {
  10. int result = -1;
  11. int fd[2],nbytes;
  12. pid_t pid;
  13. char string[WRITELEN] = "你好,管道";
  14. char readbuffer[10*K];
  15. memset(readbuffer,0,10*K);
  16. int *write_fd = &fd[1];
  17. int *read_fd = &fd[0];
  18. //建立管道
  19. result = pipe(fd);
  20. if(result == -1)
  21. {
  22. //返回-1失败
  23. printf("建立管道失败\n");
  24. return -2;
  25. }
  26. pid = fork();//分叉进程,返回两次:0表示子进程;>0表示父进程
  27. if(pid == -1)
  28. {
  29. //返回-1表示分叉进程失败
  30. printf("分叉进程失败\n");
  31. return -1;
  32. }
  33. if(pid == 0)
  34. {
  35. //0表示子进程
  36. close(*read_fd);//关闭管道读那端
  37. //写数据
  38. int writeSize = WRITELEN;
  39. result = 0;
  40. while(writeSize>=0)
  41. {
  42. result = write(*write_fd,string,writeSize);
  43. printf("result = %d\n",result);
  44. if(result>0)
  45. {
  46. writeSize -= result;
  47. printf("写入%d字节数据,剩余%d字节数据\n",result,writeSize);
  48. }
  49. else
  50. {
  51. sleep(10);
  52. break;
  53. }
  54. }
  55. return 0;
  56. }
  57. else
  58. {
  59. //表示父进程
  60. close(*write_fd);//关闭写端
  61. while(1)
  62. {
  63. memset(readbuffer,0,sizeof(readbuffer));
  64. nbytes = read(*read_fd,readbuffer,sizeof(readbuffer));
  65. if(nbytes <= 0)
  66. {
  67. printf("数据读取完毕\n");
  68. break;
  69. }
  70. printf("读取字节总数:%d 内容为:%s\n",nbytes,readbuffer);
  71. }
  72. }
  73. return 0;
  74. }
运行结果:
进程间通信和同步_第1张图片


结果分析:
可以发现父进程每次读取10K数据,读取了13次将全部数据读出。而子进程一次性地写入128K数据,当父进程将全部数据读取完毕的时候,子进程write()函数才返回。
write()函数会将数据写完之后才返回,上述操作证明了管道的操作是阻塞性质的


信号量&共享内存

ftok函数:
统建立IPC通讯(如消息队列、共享内存时)必须指定一个ID值。通常情况下,该id值通过ftok函数得到。
      
      
      
      
  1. ftok原型如下:
  2. key_t ftok( char * fname, int id )
fname就时你指定的文件名(该文件必须是存在而且可以访问的),id是子序号,虽然为int,但是只有8个比特被使用(0-255)。

当成功执行的时候,一个key_t值将会被返回,否则 -1 被返回。

在一般的UNIX实现中,是将文件的索引节点号取出,前面加上子序号得到key_t的返回值。如指定文件的索引节点号为65538,换算成16进制为 0x010002,而你指定的ID值为38,换算成16进制为0x26,则最后的key_t返回值为0x26010002。
     
     
     
     
  1. #include "unpipc.h"
  2. int main(int argc, char **argv)
  3. {
  4. struct stat stat;
  5. if (argc != 2)
  6. err_quit("usage: ftok ");
  7. Stat(argv[1], &stat);
  8. printf("st_dev: %lx, st_ino: %lx, key: %x/n",
  9. (u_long) stat.st_dev,(u_long) stat.st_ino,
  10. Ftok(argv[1], 0x57));
  11. exit(0);
  12. }

      
      
      
      
  1. 程序运行结果:
  2. [cbs@linux svipc]$ ./ftok /tmp/mysql.sock
  3. st_dev: 802, st_ino: 34219, key: 57024219

注:两进程如在pathname和proj_id上达成一致(或约定好),双方就都能够通过调用ftok函数得到同一个IPC键。
那么ftok是怎么实现的呢?《UNIX网络编程》上讲到,ftok的实现是组合了三个值:
  • pathname所在文件系统的信息(stat结构的st_dev成员)
  • pathname在本文件系统内的索引节点号(stat结构的st_ino成员)
  • id的低序8位(不能为0)
具体如何组合的,根据系统实现而不同。

使用ftok()需要注意的问题:

  • pathname指定的目录(文件)必须真实存在且调用进程可访问,否则ftok返回-1;
  • pathname指定的目录(文件)不能在程序运行期间删除或创建。因为文件每次创建时由系统赋予的索引节点可能不一样。这样一来,通过同一个pathname与proj_id就不能保证生成同一个IPC键。

信号量

首先了解一下,信号量机概念是由荷兰科学家Dijkstr引入,值得一提的是,它提出的Dijksrtr算法解决了最短路径问题。

信号量又称为信号灯,它是用来协调不同进程间的数据对象的,而最主要的应用是共享内存方式的进程间通信。本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况,信号量是一个特殊的变量,并且只有两个操作可以改变其值:等待(wait)与信号(signal)。

因为在Linux与UNIX编程中,"wait"与"signal"已经具有特殊的意义了(暂不知这特殊意义是啥),所以原始概念: 
     用于等待(wait)的P(信号量变量) ; 
     用于信号(signal)的V(信号量变量) ; 
这两字母来自等待(passeren:通过,如同临界区前的检测点)与信号(vrjgeven:指定或释放,如同释放临界区的控制权)的荷兰语。

P操作:负责把当前进程由运行状态转换为阻塞状态,直到另外一个进程唤醒它。

操作为:申请一个空闲资源(把信号量减1),若成功,则退出;若失败,则该进程被阻塞;

V操作:负责把一个被阻塞的进程唤醒,它有一个参数表,存放着等待被唤醒的进程信息。

操作为:释放一个被占用的资源(把信号量加1),如果发现有被阻塞的进程,则选择一个唤醒之。 

补充:查看共享信息的内存的命令是ipcs [-m|-s|-q] (全部的话是ipcs -a) ;查看共享信息的内存的命令是ipcs [-m|-s|-q]。

(一)系统调用函数semget()

函数原型:int semget(key_t key,int nsems,int semflg);

功能描述: 创建一个新的信号量集,或者存取一个已经存在的信号量集。

当调用semget创建一个信号量时,他的相应的semid_ds结构被初始化。ipc_perm中各个量被设置为相应 
值: 
        sem_nsems被设置为nsems所示的值;     
        sem_otime被设置为0;  
        sem_ctime被设置为当前时间

参数介绍: 

  • key:所创建或打开信号量集的键值,键值是IPC_PRIVATE,该值通常为0,创建一个仅能被进程进程给我的信号量, 键值不是IPC_PRIVATE,我们可以指定键值,例如1234;也可以一个ftok()函数来取得一个唯一的键值。 
  • nsems:创建的信号量集中的信号量的个数,该参数只在创建信号量集时有效。 
  • semflg:调用函数的操作类型,也可用于设置信号量集的访问权限,两者通过or表示:有IPC_CREAT,IPC_EXCL两种:

  • IPC_CREAT如果信号量不存在,则创建一个信号量,否则获取。
  • IPC_EXCL只有信号量不存在的时候,新的信号量才建立,否则就产生错误。

返回值说明: 

  • 如果成功,则返回信号量集的IPC标识符,其作用与信息队列识符一样。 
  • 如果失败,则返回-1,errno被设定成以下的某个值 
    • EACCES:没有访问该信号量集的权限 
    • EEXIST:信号量集已经存在,无法创建 
    • EINVAL:参数nsems的值小于0或者大于该信号量集的限制;或者是该key关联的信号量集已存在,并且nsems 
    • 大于该信号量集的信号量数 
    • ENOENT:信号量集不存在,同时没有使用IPC_CREAT 
    • ENOMEM :没有足够的内存创建新的信号量集 
    • ENOSPC:超出系统限制

图解:
进程间通信和同步_第2张图片

每个信号量都有一些相关值:

  • semval 信号量的值,一般是一个正整数,它只能通过信号量系统调用semctl函数设置,程序无法直接对它进行修改。
  • sempid 最后一个对信号量进行操作的进程的pid.
  • semcnt 等待信号量的值大于其当前值的进程数。
  • semzcnt 等待信号量的值归零的进程数。

(二)信号量的控制 semctl()

原型:int semctl(int semid,int semnum,int cmd,union semun ctl_arg);  
参数介绍: 

  • semid为信号量集引用标志符,即semget 的返回值。  
  • semnum第二个参数是信号量数目;
  • cmd表示调用该函数执行的操作,其取值和对应操作如下:

标准的IPC函数

(注意在头文件中包含semid_ds结构的定义)

IPC_STAT 把状态信息放入ctl_arg.stat中

IPC_SET 用ctl_arg.stat中的值设置所有权/许可权

IPC_RMID 从系统中删除信号量集合

单信号量操作

(下面这些宏与sem_num指定的信号量合semctl返回值相关)

GETVAL 返回信号量的值(也就是semval)

SETVAL 把信号量的值写入ctl_arg.val中

GETPID 返回sempid值

GETNCNT 返回semncnt(参考上面内容)

GETZCNT 返回semzcnt(参考上面内容)

全信号量操作

GETALL 把所有信号量的semvals值写入ctl_arg.array

SETALL 用ctl_arg.array中的值设置所有信号量的semvals 


参数arg代表一个union的semun的实例。semun是在linux/sem.h中定义的:
      
      
      
      
  1. union semun {
  2. int val; //执行SETVAL命令时使用
  3. struct semid_ds *buf; //在IPC_STAT/IPC_SET命令中使用
  4. unsigned short *array; //使用GETALL/SETALL命令时使用的指针
  5. }
联合体中每个成员都有各自不同的类型,分别对应三种不同的semctl 功能,如果semval 是SETVAL.则使用的将是ctl_arg.val.

功能:smctl函数依据command参数会返回不同的值。它的一个重要用途是为信号量赋初值,因为进程无法直接对信号量的值进行修改。

(三)信号量操作semop函数

在 Linux 下,PV 操作通过调用semop函数来实现,也只有它能对PV进行操作

调用原型:int semop(int semid,struct sembuf*sops,unsign ednsops); 
返回值:0,如果成功。-1,如果失败:errno=E2BIG(nsops大于最大的ops数目) 
EACCESS(权限不够) 
EAGAIN(使用了IPC_NOWAIT,但操作不能继续进行) 
EFAULT(sops指向的地址无效) 
EIDRM(信号量集已经删除) 
EINTR(当睡眠时接收到其他信号) 
EINVAL(信号量集不存在,或者semid无效) 
ENOMEM(使用了SEM_UNDO,但无足够的内存创建所需的数据结构) 
ERANGE(信号量值超出范围)

参数介绍:

第一个参数semid 是信号量集合标识符,它可能是从前一次的semget调用中获得的。

第二个参数是一个sembuf结构的数组每个 sembuf 结构体对应一个特定信号的操作sembuf结构在,中定义

      
      
      
      
  1. struct sembuf{
  2. usign short sem_num;/*信号量索引*/
  3. short sem_op;/*要执行的操作*/
  4. short sem_flg;/*操作标志*/
  5. }

sem_num 存放集合中某一信号量的索引,如果集合中只包含一个元素,则sem_num的值只能为0。

----------------------------------------------------------------------------------------------

Sem_op取得值为一个有符号整数,该整数实际给定了semop函数将完成的功能。包括三种情况:

      如果sem_op是负数,那么信号量将减去它的值,对应于p()操作。这和信号量控制的资源有关。如果没有使用IPC_NOWAIT,那么调用进程将进入睡眠状态,直到信号量控制的资源可以使用为止。

      如果sem_op是正数,则信号量加上它的值。对应于v()操作。这也就是进程释放信号量控制的资源。

      最后,如果sem_op是0,那么调用进程将调用sleep(),直到信号量的值为0。这在一个进程等待完全空闲的资源时使用。

----------------------------------------------------------------------------------------------

sem_flag是用来告诉系统当进程退出时自动还原操作,它维护着一个整型变量semadj(信号灯的计数器),可设置为 IPC_NOWAIT 或 SEM_UNDO 两种状态。若使用SEM_UNDO标志,则操作系统将自动释放该进程持有的信号量,从而使得另外一个进程可以继续工作。若没有这个标志,另外进程将P操作永远阻塞。因此,一般建议使用SEM_UNDo标志。

第三个参数是sembuf组成的数组中索引。参数sops指向由sembuf组成的数组,结构数组中的一员。


共享内存

什么是共享内存
顾名思义,共享内存就是允许两个不相关的进程访问同一个逻辑内存。共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常安排为同一段物理内存。进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址,就好像它们是由用C语言函数malloc分配的内存一样。而如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。
特别提醒共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取。所以我们通常需要用其他的机制来同步对共享内存的访问,例如前面说到的信号量。

共享内存的使得
与信号量一样,在Linux中也提供了一组函数接口用于使用共享内存,而且使用共享共存的接口还与信号量的非常相似,而且比使用信号量的接口来得简单。它们声明在头文件 sys/shm.h中

shmget函数
该函数用来创建共享内存,它的原型为:
      
      
      
      
  1. int shmget(key_t key, size_t size, int shmflg);
第一个参数,与信号量的semget函数一样,程序需要提供一个参数key(非0整数),它有效地为共享内存段命名,shmget函数成功时返回一个与key相关的共享内存标识符(非负整数),用于后续的共享内存函数。调用失败返回-1.

不相关的进程可以通过该函数的返回值访问同一共享内存,它代表程序可能要使用的某个资源,程序对所有共享内存的访问都是间接的,程序先通过调用shmget函数并提供一个键,再由系统生成一个相应的共享内存标识符(shmget函数的返回值),只有shmget函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。

第二个参数,size以字节为单位指定需要共享的内存容量

第三个参数,shmflg是权限标志,它的作用与open函数的mode参数一样,如果要想在key标识的共享内存不存在时,创建它的话,可以与IPC_CREAT做或操作。共享内存的权限标志与文件的读写权限一样,举例来说,0644,它表示允许一个进程创建的共享内存被内存创建者所拥有的进程向共享内存读取和写入数据,同时其他用户创建的进程只能读取共享内存。

shmat函数
第一次创建完共享内存时,它还不能被任何进程访问,shmat函数的作用就是用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间。它的原型如下:
      
      
      
      
  1. void *shmat(int shm_id, const void *shm_addr, int shmflg);
第一个参数,shm_id是由shmget函数返回的共享内存标识。
第二个参数,shm_addr指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。
第三个参数,shm_flg是一组标志位,通常为0。

调用成功时返回一个指向共享内存第一个字节的指针,如果调用失败返回-1.

shmdt函数
该函数用于将共享内存从当前进程中分离。注意,将共享内存分离并不是删除它,只是使该共享内存对当前进程不再可用。它的原型如下:
       
       
       
       
  1. int shmdt(const void *shmaddr);
shmctl函数
与信号量的semctl函数一样,用来控制共享内存,它的原型如下:
       
       
       
       
  1. int shmctl(int shm_id, int command, struct shmid_ds *buf);
第一个参数,shm_id是shmget函数返回的共享内存标识符。

第二个参数,command是要采取的操作,它可以取下面的三个值 :
    IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。
    IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值
    IPC_RMID:删除共享内存段

第三个参数,buf是一个结构指针,它指向共享内存模式和访问权限的结构。
shmid_ds结构至少包括以下成员:
         
         
         
         
  1. struct shmid_ds
  2. {
  3. uid_t shm_perm.uid;
  4. uid_t shm_perm.gid;
  5. mode_t shm_perm.mode;
  6. };

下面的代码在父进程和子进程之间利用共享内存进行通信,父进程向共享内存中写入数据,子进程读取数据。两个进程之间的控制采用信号量的方法,父进程写入数据成功后,信号量加1,子进程在访问信号量之前先等待信号。

  • 首先子进程会进行V操作,V操作会等待父进程会将共享内存中进行赋值操作完成后的P操作
  • 父进程P操作完成后,会进行V操作,等待子进程P操作,子进程会进行输出共享内存中的内容
  • 在子进程P操作后,子进程退出,父进程V操作等待结束,进行销毁信号量后退出
        
        
        
        
  1. #include
  2. #include
  3. #include
  4. #include
  5. #include
  6. static char msg[] = "你好,共享内存~~~\n";
  7. typedef int sem_t;
  8. union semun{
  9. int val;
  10. struct semid_ds *buf;
  11. unsigned short *array;
  12. }arg;
  13. //创建信号量
  14. int CreateSem(key_t key,int value)
  15. {
  16. union semun sem;//信号量结构体
  17. sem_t semid;
  18. sem.val = value;
  19. semid = semget(key,1,IPC_CREAT|0666);//获得信号量的ID
  20. if(semid == -1)
  21. {
  22. printf("获得信号量失败\n");
  23. return -1;
  24. }
  25. semctl(semid,0,SETVAL,sem);
  26. return semid;
  27. }
  28. //利用semop()构建基本的P操作
  29. int Sem_V(sem_t semid)
  30. {
  31. struct sembuf sops = {0,1,SEM_UNDO};//对信号量0进行加1操作
  32. return semop(semid,&sops,1);
  33. }
  34. //利用semop()构建基本的V操作
  35. int Sem_P(sem_t semid)
  36. {
  37. struct sembuf sops = {0,-1,SEM_UNDO};//对信号量0进行减1操作
  38. return semop(semid,&sops,1);
  39. }
  40. //销毁信号量
  41. void DestroySem(sem_t semid)
  42. {
  43. union semun sem;//信号量结构体
  44. sem.val = 0;
  45. semctl(semid,0,IPC_RMID,sem);//命令IPC_RMID将给定的信号量销毁
  46. }
  47. int main(void)
  48. {
  49. key_t key;
  50. int semid,shmid;
  51. char i,*shms,*shmc;
  52. struct semid_ds buf;
  53. int value = 0;
  54. char buffer[80];
  55. pid_t p;
  56. struct stat stat_info;
  57. char path[256];
  58. memset(path,0,256);
  59. strcpy(path,"/etc/environment");
  60. //sprintf(path, "/etc/environment", (char*)getenv("HOME"));
  61. printf("path = %s\n",path);
  62. if(stat(path,&stat_info)!=0)
  63. {
  64. printf("Error\n");
  65. return -1;
  66. }
  67. key=ftok(path, 'a');//生成键值
  68. printf("key = %d\n",key);
  69. shmid = shmget(key,1024,IPC_CREAT|0604);//获得共享内存,大小1024
  70. printf("共享内存地址为:0x%08x\n",shmid);
  71. semid = CreateSem(key,0);//建立信号量
  72. p = fork();
  73. if(p>0)
  74. {
  75. //父进程
  76. shms = (char*)shmat(shmid,0,0);//挂载共享内存
  77. //Sem_V(semid);//减小信号量
  78. memset(shms,0,1024);
  79. memcpy(shms,msg,strlen(msg)+1);//复制内容
  80. printf("11:信号量值=%d\n",semctl(semid,0,GETVAL));
  81. sleep(10);
  82. printf("12:信号量值=%d\n",semctl(semid,0,GETVAL));
  83. Sem_V(semid);//获得共享内存信号量=======V操作1
  84. shmdt(shms);//摘除共享内存
  85. Sem_P(semid);//减小信号量=======P操作1 会等待子进程中V操作2
  86. DestroySem(semid);//销毁信号量
  87. printf("父进程End\n");
  88. }
  89. else if(p==0)
  90. {
  91. //子进程
  92. printf("21:信号量值=%d\n",semctl(semid,0,GETVAL));
  93. Sem_P(semid);//减小信号量=======P操作2 会等待父进程中V操作1
  94. printf("22:信号量值=%d\n",semctl(semid,0,GETVAL));
  95. shmc = (char*)shmat(shmid,0,0);//挂载共享内存
  96. printf("共享内存的值为:%s\n",shmc);
  97. shmdt(shmc);//摘除共享内存
  98. printf("子进程End\n");
  99. Sem_V(semid);//=======V操作2
  100. return 0;
  101. }
  102. return 0;
  103. }

执行结果:

进程间通信和同步_第3张图片


结果分析:
可以看出“ 22:信号量值 ”是在sleep 10秒后“ 12:信号量值 ”输出之后输出的。所以P操作2在等待父进程中的V操作1结束之后才开始执行子进程中的内容。

你可能感兴趣的:(linux)