进程间通信 (ipc机制 ):管道,信号量,共享内存,消息队列,套接字。
管道
-
管道:管道可以用来在两个进程之间通信(传递数据),如: ps -ef | grep “bash”, 其中‘|’就是管 道,其作用就是将 ps 命令的结果写入管道文件,然后 grep 再从管道文件中读出该数据进行 过滤。
-
管道文件:本身在磁盘上只保存其各种属性,打开其(open)会在内存中分配空间,即写入管道的数据存在于内存中,因此效率比较高,只存在一个读端,一个写端。
-
管道的通信方式:半双工。
有名管道
有名管道可以在任意两个进程之间通信 有名管道的创建:
-
命令创建: mkfifo FIFO
-
系统调用创建
lcx@lcx-virtual-machine:~/mycode/11.8$ mkfifo fifo lcx@lcx-virtual-machine:~/mycode/11.8$ ls fifo lcx@lcx-virtual-machine:~/mycode/11.8$ ls -l 总用量 0 prw-rw-r-- 1 lcx lcx 0 11月 8 20:10 fifo lcx@lcx-virtual-machine:~/mycode/11.8$
例如:一个进程写文件一个进程读文件。
lcx@lcx-virtual-machine:~/mycode/11.8$ vi a.c 1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<unistd.h> 4 #include<string.h> 5 #include<assert.h> 6 #include<fcntl.h> 7 int main() 8 { 9 int fdw=open("fifo",O_WRONLY); 10 assert(fdw!=-1); 11 printf("fdw=%d\n",fdw); 12 char buff[128]={0}; 13 printf("input:\n"); 14 while(1) 15 { 16 fgets(buff,128,stdin); 17 if(strncmp(buff,"end",3)==0) 18 { 19 break; 20 } 21 write(fdw,buff,strlen(buff)); 22 } 23 close(fdw); 24 exit(0); 25 } ~ ~ ~ lcx@lcx-virtual-machine:~/mycode/11.8$ gcc -o a a.c lcx@lcx-virtual-machine:~/mycode/11.8$ ls a a.c fifo lcx@lcx-virtual-machine:~/mycode/11.8$ vi b.c 1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<unistd.h> 4 #include<string.h> 5 #include<assert.h> 6 #include<fcntl.h> 7 int main() 8 { 9 int fdr=open("fifo",O_RDONLY); 10 assert(fdr!=-1); 11 printf("fdr=%d\n",fdr); 12 while(1) 13 { 14 char buff[128]={0}; 15 int n=read(fdr,buff,127); 16 if(n==0)//当写入程序关闭时,读取程序最后不会再阻塞住,而是返回为0 17 { 18 break; 19 } 20 printf("buff=%s\n",buff); 21 } 22 close(fdr); 23 exit(0); 24 } ~ ~ ~ lcx@lcx-virtual-machine:~/mycode/11.8$ gcc -o b b.c lcx@lcx-virtual-machine:~/mycode/11.8$ ls a a.c b b.c fifo lcx@lcx-virtual-machine:~/mycode/11.8$ ./a fdw=3 input: hello //在另一终端运行: lcx@lcx-virtual-machine:~/mycode/11.8$ ./b fdr=3 buff=hello n=6
注:
-
要打开管道文件必须有读和写俩个进程同时存在才行,否则会阻塞。
-
如果管道为空,读取便会阻塞。
-
如果管道为满,写入便会阻塞。
-
一个管道在传递数据是单向传递的。
-
管道的写段关闭,读端最终返回值为0。
-
管道的读端关闭,写段写入数据会触发异常,内核发出信号(SIGPIPE)处理写段。
无名管道
只能在父子进程间的通信。
无名管道的创建:
int pipe(int fds[2]);
-
pipe()成功返回 0,失败返回-1
-
fds[0]是管道读端的描述符
-
fds[1]是管道写端的描述符
例如:
lcx@lcx-virtual-machine:~/mycode/11.8$ vi main.c 1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<unistd.h> 4 #include<string.h> 5 #include<assert.h> 6 #include<fcntl.h> 7 int main() 8 { 9 int fd[2]; 10 int res=pipe(fd); 11 assert(res!=-1); 12 pid_t pid=fork(); 13 assert(pid!=-1); 14 if(pid==0) 15 { 16 close(fd[1]);//关掉写段 17 char buff[128]={0}; 18 while(1) 19 { 20 int n=read(fd[0],buff,127); 21 if (n==0) 22 { 23 break; 24 } 25 printf("child read:%s\n",buff); 26 } 27 close(fd[0]);//关掉读端 28 } 29 else 30 { 31 close(fd[0]);//关掉读端 32 char buff[128]={0}; 33 while(1) 34 { 35 fgets(buff,128,stdin); 36 if(strncmp(buff,"end",3)==0) 37 { 38 break; 39 } 40 write(fd[1],buff,strlen(buff)); 41 } 42 close(fd[1]);//关掉写段 43 } 44 exit(0); 45 } ~ lcx@lcx-virtual-machine:~/mycode/11.8$ gcc -o main main.c lcx@lcx-virtual-machine:~/mycode/11.8$ ./main as child read:as da child read:da fe child read:fe asfe child read:asfe qeertrhrg child read:qeertrhrg end
注:
-
使用无名管道通信时,父子进程进入时,应先将不需要的读端或写段关闭,以保证仅一个读端一个写段。
通信方式
单工:单向传递,不可逆。(即收只能收,发只能发)
-
半双工:单向传递,可逆。(即不能同时收和发)
-
全双工:双向传递,收和发同时在进行。
信号量
信号量描述
-
信号量主要用来同步进程。
-
信号量是一个特殊的变量,一般取正数值。它的值代表允许访问的资源数目,
-
信号量的值如果只取 0,1,将其称为二值信号量,0代表无资源可用,1代表有资源可用。
-
获取资源 时,需要对信号量的值进行原子减一,该操作被称为 P 操作。当信号量值为 0 时,代表没有资源可用,P 操作会阻塞。
-
释放资源时,需要对信号量的值进行原子加一,该操作被称为 V 操作。
-
如果信号量的值大于 1,则称之为计数信号量。
-
临界资源:同一时刻,只允许被一个进程或线程访问的资源 。
-
临界区:访问临界资源的代码段。
信号量的封装:
-
sem_init() -> 初始化
-
sem_p() -> p操作
-
sem_v() -> v操作
-
sem_destroy() ->释放
linux提供的接口:
-
int semget(key_t key, int nsems, int semflg);
-
semget()创建或者获取已存在的信号量的键;
-
semget()成功返回信号量的 ID, 失败返回-1 ;
-
key:两个进程使用相同的 key 值,就可以使用同一个信号量 ;
-
nsems:内核维护的是一个信号量集,在新建信号量时,其指定信号量集中信号 量的个数 ;
-
semflg 可选: IPC_CREAT IPC_EXCL;
-
int semctl(int semid, int semnum, int cmd, ...);
-
semctl()控制信号量 ,即初始化及释放
-
semctl()成功返回 0,失败返回-1
-
cmd 选项: SETVAL IPC_RMID
-
union semun {
-
int val;
-
struct semid_ds *buf;
-
unsigned short *array;
-
struct seminfo *_buf;};
-
-
int semop(int semid, struct sembuf sops, unsigned nsops);
-
semop()对信号量进行改变,做 P 操作或者 V 操作
-
semop()成功返回 0,失败返回-1
-
struct sembuf
-
{
-
unsigned short sem_num; //指定信号量集中的信号量下标
-
short sem_op; //其值为-1,代表 P 操作,其值为 1,代表 V 操作
-
short sem_flg; //SEM_UNDO
-
-
};
信号量的应用
例:两个进程通过一个信号量访问一个临界资源:a,b
//sem.h 封装头文件: lcx@lcx-virtual-machine:~/mycode/11.10$ vi sem.h 1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<unistd.h> 4 #include<assert.h> 5 #include<string.h> 6 #include<sys/sem.h> 7 8 union semun //由于该联合体未定义,需要自行进行定义 9 { 10 int val; 11 }; 12 13 void sem_init();//初始化信号量 14 void sem_p();//p操作 15 void sem_v();//v操作 16 void sem_destroy();//释放掉信号量 17 ~ //sem.c 封装信号量: lcx@lcx-virtual-machine:~/mycode/11.10$ vi sem.c 1 #include"sem.h" //引用头文件 2 static int semid=-1; //定义静态全局变量 semid 信号量的id ,仅本文件可见 3 void sem_init() //初始化 4 { 5 semid=semget((key_t)1234,1,IPC_CREAT|IPC_EXCL|0600); //创建信号量:1234为共同使用的的key,1为信号量数目,命令为创建一个全新的信号量并给权限0600 6 if(semid==-1)//创建失败 7 { 8 semid=semget((key_t)1234,1,0600);//可能是1234被占用,获取被创建好的信号量 不用初始化 9 if(semid==-1) //再次失败 10 { 11 printf("semget err\n"); //打印创建失败 12 return ; 13 } 14 } 15 else 16 { 17 union semun a;//初始化,定义联合体a 18 a.val=1; //初始化val的值为1 19 if (semctl(semid,0,SETVAL,a)==-1)//初始化 semid信号量id号,第0个信号量,命令初始化信号量值为 a 20 { 21 printf("semctl err\n");//-1,初始化错误 22 } 23 } 24 } 25 void sem_p()//p操作 26 { 27 struct sembuf buf;//定义一个结构体变量 28 buf.sem_num=0;//第0个信号量 29 buf.sem_op=-1;//代表需要p操作 30 buf.sem_flg=SEM_UNDO;//防止该程序在释放信号量前死掉,导致其他需要用该信号量的进程一直等待。命令由内核归还信号量 31 if(semop(semid,&buf,1)==-1)//p操作:信号量id,结构体,对几个进行信号量进行操作,即结构体数目 32 { 33 printf("semop p err\n"); 34 } 35 36 } 37 void sem_v() 38 { 39 struct sembuf buf; 40 buf.sem_num=0; 41 buf.sem_op=1;//代表需要v操作 42 buf.sem_flg=SEM_UNDO; 43 if(semop(semid,&buf,1)==-1)//v操作 44 { 45 printf("semop v err\n"); 46 } 47 } 48 void sem_destroy()//释放信号量 49 { 50 if (semctl(semid,0,IPC_RMID)==-1)//semid信号量id号,第0个信号量 ,命令释放信号量 51 { 52 printf("semctl err\n"); 53 } 54 } //a进程 lcx@lcx-virtual-machine:~/mycode/11.10$ vi a.c 1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<unistd.h> 4 #include<string.h> 5 #include<assert.h> 6 #include"sem.h" 7 int main() 8 { 9 sem_init();//创建或获取并初始化信号量 10 for(int i=0;i<5;i++) 11 { 12 sem_p();//p操作-1,此时为0,代表没有资源 13 printf("A");//代表开始使用该临界资源 14 fflush(stdout); 15 int n=rand()%3; 16 sleep(n); 17 printf("A");//代表推出使用临界资源 18 fflush(stdout); 19 sem_v();//v操作+1,此时为1,代表有资源可用 20 n=rand()%3; 21 sleep(n); 22 } 23 sleep(10);//预测十秒后b也使用完了在进行释放信号量 24 sem_destroy();//释放信号量 25 } ~ ~ //b进程 lcx@lcx-virtual-machine:~/mycode/11.10$ vi b.c 1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<unistd.h> 4 #include<string.h> 5 #include<assert.h> 6 #include"sem.h" 7 int main() 8 { 9 sem_init(); 10 for(int i=0;i<5;i++) 11 { 12 sem_p(); 13 printf("B"); 14 fflush(stdout); 15 int n=rand()%3; 16 sleep(n); 17 printf("B"); 18 fflush(stdout); 19 sem_v(); 20 n=rand()%3; 21 sleep(n); 22 } 23 } ~ ~ //运行进程 lcx@lcx-virtual-machine:~/mycode/11.10$ gcc -o a a.c sem.c lcx@lcx-virtual-machine:~/mycode/11.10$ gcc -o b b.c sem.c lcx@lcx-virtual-machine:~/mycode/11.10$ ./a& ./b& [1] 17377 [2] 17378 lcx@lcx-virtual-machine:~/mycode/11.10$ AABBAABBAABBAABBAABB //此时两个进程A,Bza
例2:三个进程:打印A,打印B,打印C,要求最后输出 ABCABCABC....
解析:因为有顺序的打印,则需要进行三个信号量进行控制:S1,S2,S3;
初始Sa 为1,Sb为0,Sc为0;解题如下:
//sem.h 封装头文件: 1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<unistd.h> 4 #include<string.h> 5 #include<sys/sem.h> 6 7 #define SEM_NUM 3 //需要三个信号量,定义宏 8 #define SEM1 0 //第一个信号量,p v 操作需要传参 9 #define SEM2 1 //第二个信号量,p v 操作需要传参 10 #define SEM3 2 //第三个信号量,p v 操作需要传参 11 union semun //定义联合体 12 { 13 int val; 14 }; 15 int sem_init(); //创建初始化 16 void sem_p(int index); //P操作 17 void sem_v(int index); //V操作 18 void sem_destroy(); //释放信号量 ~ ~ //sem.c 封装信号量: 1 #include"sem.h" 2 static int semid=-1; 3 int sem_init() 4 { 5 semid=semget((key_t)1234,SEM_NUM,IPC_CREAT|IPC_EXCL|0600);//与上道题不同的是,这道题中信号量数目为3 6 if (semid==-1) 7 { 8 semid=semget((key_t)1234,SEM_NUM,0600); 9 if (semid==-1) 10 { 11 printf("semget err\n"); 12 return -1; 13 } 14 } 15 else 16 { 17 int arr[SEM_NUM]={1,0,0}; //初始化信号量准备工作:A为1,B为0,C为0 18 for (int i=0;i<SEM_NUM;i++) //用循环进行初始化 19 { 20 union semun a; 21 a.val=arr[i]; //获得信号量初始化值 22 if(semctl(semid,i,SETVAL,a)==-1) //初始化:id,第i个信号量,赋值a 23 { 24 printf("semctl err\n"); 25 return -1; 26 } 27 } 28 } 29 } 30 void sem_p(int index) 31 { 32 if (index<0||index>=SEM_NUM) //判断参数合法性 33 { 34 return; 35 } 36 struct sembuf buf; 37 buf.sem_num=index; //几号信号量 38 buf.sem_op=-1; //需要进行P操作 39 buf.sem_flg=SEM_UNDO; 40 41 if (semop(semid,&buf,1)==-1); //p操作,只操作一个信号量 42 { 43 return; 44 } 45 } 46 void sem_v(int index) 47 { 48 if (index<0||index>=SEM_NUM) 49 { 50 return; 51 } 52 struct sembuf buf; 53 buf.sem_num=index; 54 buf.sem_op=1; 55 buf.sem_flg=SEM_UNDO; 56 57 if (semop(semid,&buf,1)==-1); 58 { 59 return; 60 } 61 } 62 void sem_destroy() 63 { 64 if(semctl(semid,0,IPC_RMID)==-1)//直接进行释放,0此时只是占位。 65 { 66 printf("semctl err\n"); 67 return ; 68 } 69 } //a进程 lcx@lcx-virtual-machine:~/mycode/11.13$ vi a.c 1 #include"sem.h" 2 3 int main() 4 { 5 int res=sem_init();//获取返回值,如果为-1,则失败 6 if(res==-1) 7 { 8 return 0; 9 } 10 for(int i=0;i<5;i++)//打印5次 11 { 12 sem_p(SEM1);//对S1 p操作,a进程开始执行 13 printf("A"); 14 fflush(stdout); 15 sleep(1); 16 sem_v(SEM2);//对S2 v操作,此时S2为1代表可用 17 } 18 } ~ ~ ~ //b进程 lcx@lcx-virtual-machine:~/mycode/11.13$ vi a.c 1 #include"sem.h" 2 3 int main() 4 { 5 int res=sem_init(); 6 if(res==-1) 7 { 8 return 0; 9 } 10 for(int i=0;i<5;i++) 11 { 12 sem_p(SEM2);//对S2 P操作,执行B 13 printf("B"); 14 fflush(stdout); 15 sleep(1); 16 sem_v(SEM3);//对S3 v操作,此时S3为1代表可用 17 } 18 } ~ //c进程 lcx@lcx-virtual-machine:~/mycode/11.13$ vi a.c 1 #include"sem.h" 2 3 int main() 4 { 5 int res=sem_init(); 6 if(res==-1) 7 { 8 return 0; 9 } 10 for(int i=0;i<5;i++) 11 { 12 sem_p(SEM3);对S3 P操作,执行C 13 printf("C"); 14 fflush(stdout); 15 sleep(1); 16 sem_v(SEM1); 17 } 18 sem_destroy();对S1 v操作,此时S1为1代表可用 19 } ~ ~ //运行进程 lcx@lcx-virtual-machine:~/mycode/11.13$ gcc -o a a.c sem.c lcx@lcx-virtual-machine:~/mycode/11.13$ gcc -o b b.c sem.c lcx@lcx-virtual-machine:~/mycode/11.13$ gcc -o c c.c sem.c lcx@lcx-virtual-machine:~/mycode/11.13$ ls a a.c b b.c c c.c sem.c sem.h sem.o lcx@lcx-virtual-machine:~/mycode/11.13$ ./a& ./b& ./c& [1] 12031 [2] 12032 [3] 12033 lcx@lcx-virtual-machine:~/mycode/11.13$ ABCABCABCABCABC
共享内存
概念:共享内存为多个进程之间共享和传递数据提供了一种有效的方式。共享内存是先在物理 内存上申请一块空间,多个进程可以将其映射到自己的虚拟地址空间中。所有进程都可以访问共享内存中的地址,就好像它们是由 malloc 分配的一样。如果某个进程向共享内存写入了 数据,所做的改动将立刻被可以访问同一段共享内存的任何其他进程看到。由于它并未提供 同步机制,所以我们通常需要用其他的机制来同步对共享内存的访问。
注:
-
共享内存由内核管理,如果不给内核释放共享内存的信息,程序终止时,只会自动断开映射,并不会收回这块共性内存,该共享内存仍然存在。
-
由于使用共享内存传递数据时存在缺陷,因此通常在使用共享内存时必定伴随着使用信号量,用来同步两个进程。
使用方法:
-
创建共享内存
int shmget(key_t key, size_t size, int shmflg);
-
shmget()用于创建或者获取共享内存
-
shmget()成功返回共享内存的 ID, 失败返回-1
-
key: 不同的进程使用相同的 key 值可以获取到同一个共享内存
-
size: 创建共享内存时,指定要申请的共享内存空间大小
-
shmflg: IPC_CREAT IPC_EXCL
-
-
将共享内存映射到进程中
void shmat(int shmid, const void * shmaddr, int shmflg);
-
shmat()将申请的共享内存的物理内存映射到当前进程的虚拟地址空间上
-
shmat()成功返回返回共享内存的首地址,失败返回 NULL
-
shmaddr:一般给 NULL,由系统自动选择映射的虚拟地址空间
-
shmflg: 一般给 0, 可以给 SHM_RDONLY 为只读模式,其他的为读写
-
-
断开映射
int shmdt(const void *shmaddr);
-
shmdt()断开当前进程的 shmaddr 指向的共享内存映射
-
shmdt()成功返回 0, 失败返回-1
-
-
删除共享内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
-
shmctl()控制共享内存
-
shmctl()成功返回 0,失败返回-1
-
cmd: IPC_RMID
-
共享内存的应用
例:A进程从键盘获取数据写入共享内存,B进程输出。
//信号量封装 //由于使用共享内存存在缺陷,需要通过信号量来同步进程 lcx@lcx-virtual-machine:~/mycode/11.13$ vi sem.h 1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<unistd.h> 4 #include<string.h> 5 #include<sys/sem.h> 6 7 #define SEM_NUM 2 8 #define SEM1 0 9 #define SEM2 1 11 union semun 12 { 13 int val; 14 }; 15 int sem_init(); 16 void sem_p(int index); 17 void sem_v(int index); 18 void sem_destroy(); ~ lcx@lcx-virtual-machine:~/mycode/11.13$ vi sem.c 1 #include"sem.h" 2 static int semid=-1; 3 int sem_init() 4 { 5 semid=semget((key_t)1234,SEM_NUM,IPC_CREAT|IPC_EXCL|0600);//与上道题不同的是,这道题中信号量数目为2 6 if (semid==-1) 7 { 8 semid=semget((key_t)1234,SEM_NUM,0600); 9 if (semid==-1) 10 { 11 printf("semget err\n"); 12 return -1; 13 } 14 } 15 else 16 { 17 int arr[SEM_NUM]={1,0}; //初始化信号量准备工作:A为1,B为0 18 for (int i=0;i<SEM_NUM;i++) //用循环进行初始化 19 { 20 union semun a; 21 a.val=arr[i]; //获得信号量初始化值 22 if(semctl(semid,i,SETVAL,a)==-1) //初始化:id,第i个信号量,赋值a 23 { 24 printf("semctl err\n"); 25 return -1; 26 } 27 } 28 } 29 } 30 void sem_p(int index) 31 { 32 if (index<0||index>=SEM_NUM) //判断参数合法性 33 { 34 return; 35 } 36 struct sembuf buf; 37 buf.sem_num=index; //几号信号量 38 buf.sem_op=-1; //需要进行P操作 39 buf.sem_flg=SEM_UNDO; 40 41 if (semop(semid,&buf,1)==-1); //p操作,只操作一个信号量 42 { 43 return; 44 } 45 } 46 void sem_v(int index) 47 { 48 if (index<0||index>=SEM_NUM) 49 { 50 return; 51 } 52 struct sembuf buf; 53 buf.sem_num=index; 54 buf.sem_op=1; 55 buf.sem_flg=SEM_UNDO; 56 57 if (semop(semid,&buf,1)==-1); 58 { 59 return; 60 } 61 } 62 void sem_destroy() 63 { 64 if(semctl(semid,0,IPC_RMID)==-1)//直接进行释放,0此时只是占位。 65 { 66 printf("semctl err\n"); 67 return ; 68 } 69 } //a进程 lcx@lcx-virtual-machine:~/mycode/11.13$ vi shma.c 1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<unistd.h> 4 #include<string.h> 5 #include<assert.h> 6 #include<sys/shm.h> 7 #include"sem.h" 8 int main() 9 { 10 int shmid =shmget((key_t)1234,128,IPC_CREAT|0600); //创建共享内存:键值,字节数,命令|权限 11 assert(shmid!=-1); 13 char* s=(char*)shmat(shmid,NULL,0); //将共享内存映射过来 14 sem_init();//创建初始化信号量 15 while(1) 16 { 17 printf("input:\n"); 18 char buff[128]={0};//定义字符数组用来存放键盘输入的字符 19 fgets(buff,128,stdin);//获取键盘输入字符 20 sem_p(SEM1);//对S1 P操作,A进程执行 21 strcpy(s,buff);//将字符数组拷贝入共享内存中 22 sem_v(SEM2);//对S2 V操作,此时S2为1,B进程可使用 23 if(strncmp(buff,"end",3)==0)//注:1.在拷贝后进行判断,因为B进程需要end标志; 24 { // 2.在V操作后判断,如果在V前,end结束后,B进程没资源可用一直堵塞住 25 break; 26 } 27 } 28 shmdt(s);//断开映射:共享内存地址 29 } //b进程 lcx@lcx-virtual-machine:~/mycode/11.13$ vi shmb.c 1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<unistd.h> 4 #include<string.h> 5 #include<assert.h> 6 #include<sys/shm.h> 7 #include"sem.h" 8 int main() 9 { 10 int shmid =shmget((key_t)1234,128,IPC_CREAT|0600);//创建共享内存 11 assert(shmid!=-1); 12 13 char* s=(char*)shmat(shmid,NULL,0);//将共享内存映射过来 14 sem_init();//创建获取初始化信号量 15 while(1) 16 { 17 sem_p(SEM2);//S2 P操作,B进程开始执行 18 if(strncmp(s,"end",3)==0)//判断退出循环条件 19 { 21 break; 22 } 23 printf("read:%s\n",s);//打印共享内存中的数据 24 sem_v(SEM1);//S1 V操作,此时S1为1,A进程可使用 25 } 26 shmdt(s);//断开映射 27 shmctl(shmid,IPC_RMID,NULL);//释放信号量 28 sem_destroy();//s 29 } //执行 lcx@lcx-virtual-machine:~/mycode/11.13$ gcc -o shma shma.c sem.c lcx@lcx-virtual-machine:~/mycode/11.13$ gcc -o shmb shmb.c sem.c lcx@lcx-virtual-machine:~/mycode/11.13$ ./shma //另一个终端中lcx@lcx-virtual-machine:~/mycode/11.13$ ./shmb
消息队列
在内存中开辟空间用来:
-
发送消息
-
数据类型
-
数据
-
-
获取消息
-
数据类型
-
数据
-
使用方法:
-
msgget()创建或者获取一个消息队列
int msgget(key_t key, int msqflg);
-
msgget()成功返回消息队列 ID,失败返回-1
-
msqflg: IPC_CREAT
-
-
msgsnd()发送一条消息
int msgsnd(int msqid, const void *msqp, size_t msqsz, int msqflg);
-
msqp消息结构为:
{
long mtype; 消息类型, 必须大于 0
char mtext[1]; // 消息数据
};
-
msgsnd()成功返回 0, 失败返回-1
-
msqsz: 指定 msqp 中数据的大小 。注:是结构体数据元素的大小,不是结构体的大小。
-
msqflg:一般设置为 0 可以设置 IPC_NOWAIT
-
-
msgrcv()接收一条消息
ssize_t msgrcv(int msqid, void *msqp, size_t msqsz, long msqtyp, int msqflg);
-
msgrcv()接收一条消息
-
msgrcv()成功返回 mtext 中接收到的数据长度, 失败返回-1
-
msqid:消息队列id
-
msqp:用来存放获取到的消息的空间
-
msqsz:用来存放获取到的消息的空间大小
-
msqtyp: 指定接收的消息类型,类型可以为 0
-
msqflg: 一般设置为 0 可以设置 IPC_NOWAIT
-
-
msgctl()控制消息队列
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
-
msgctl()控制消息队列
-
msgctl()成功返回 0,失败返回-1
-
cmd: IPC_RMID
-
消息队列的应用:
创建一个消息队列,发送一条消息,接收一条消息
//写入消息 lcx@lcx-virtual-machine:~/mycode/11.22$ vi a.c 1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<unistd.h> 4 #include<string.h> 5 #include<assert.h> 6 #include<sys/msg.h> 7 struct mess //定义消息结构体 8 { 9 long type; 10 char buff[32]; 11 }; 12 int main() 13 { 14 int msgid=msgget((key_t)1234,IPC_CREAT|0600);//创建消息队列 15 assert(msgid!=-1); 16 struct mess dt;//定义一个消息变量 17 dt.type=2;//消息类型赋为2 18 strcpy(dt.buff,"hello2");//消息数据为hello2 19 msgsnd(msgid,(void *)&dt,32,0);//写入消息队列 20 21 } ~ ~ //读取消息 lcx@lcx-virtual-machine:~/mycode/11.22$ vi b.c 1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<unistd.h> 4 #include<string.h> 5 #include<assert.h> 6 #include<sys/msg.h> 7 struct mess//定义消息结构体 8 { 9 long type; 10 char buff[32]; 11 }; 12 int main() 13 { 14 int msgid=msgget((key_t)1234,IPC_CREAT|0600);//创建或打开消息队列 15 assert(msgid!=-1); 16 struct mess dt;//定义消息变量用来存放读出的消息 17 msgrcv(msgid,&dt,32,0,0);//读取消息:32为消息中数据的大小,0为消息类型:可读任意消息类型消息,写消息时不能为0 ,若为1,2,等等,只能读对应类型的消息 18 printf("read msg:%s\n",dt.buff);//打印读出的消息 19 } ~ lcx@lcx-virtual-machine:~/mycode/11.22$ gcc -o b b.c lcx@lcx-virtual-machine:~/mycode/11.22$ gcc -o a a.c lcx@lcx-virtual-machine:~/mycode/11.22$ ./a //写入一次 lcx@lcx-virtual-machine:~/mycode/11.22$ ./a //写入第二次 lcx@lcx-virtual-machine:~/mycode/11.22$ ipcs -q //此时占用字节为64 --------- 消息队列 ----------- 键 msqid 拥有者 权限 已用字节数 消息 0x000004d2 0 lcx 600 64 2 lcx@lcx-virtual-machine:~/mycode/11.22$ ./b //读出第一次 read msg:hello2 lcx@lcx-virtual-machine:~/mycode/11.22$ ./b //读出第二次 read msg:hello2 lcx@lcx-virtual-machine:~/mycode/11.22$ ipcs -q //此时消息队列中无可读消息 --------- 消息队列 ----------- 键 msqid 拥有者 权限 已用字节数 消息 0x000004d2 0 lcx 600 0 0
注:当消息队列为空时,读消息会被阻塞。
观察
观察方法:ipcs
//终端1:运行三个进程 lcx@lcx-virtual-machine:~/mycode/11.13$ ./a& ./b& ./c& //终端2:观察信号量 lcx@lcx-virtual-machine:~/mycode/11.13$ ipcs --------- 消息队列 ----------- 键 msqid 拥有者 权限 已用字节数 消息 ------------ 共享内存段 -------------- 键 shmid 拥有者 权限 字节 连接数 状态 0x00000000 9 lcx 600 524288 2 目标 0x00000000 16 lcx 600 4194304 2 目标 0x00000000 19 lcx 600 524288 2 目标 0x00000000 20 lcx 600 524288 2 目标 --------- 信号量数组 ----------- 键 semid 拥有者 权限 nsems 0x000004d2 1 lcx 600 3
观察信号量: ipcs -s
观察共享内存:ipcs -m
观察消息队列:ipcs -q
删除:
-
ipcrm -s +id :删除id信号量
-
ipcrm -m +id :删除id共享内存
-
ipcrm -q +id :删除消息队列