linux系统编程——进程通信

进程间通信——IPC

参考链接:https://blog.csdn.net/wh_sjc/article/details/70283843
进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。
IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。其中 Socket和Streams支持不同主机上的两个进程IPC。
以Linux中的C语言编程为例。
https://blog.csdn.net/wh_sjc/article/details/70283843

一.管道

一、管道
管道,通常指无名管道,是 UNIX 系统IPC最古老的形式。

1、特点:
它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。

它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。

它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。

原型

#include <unistd.h>
int pipe(int pipefd[2]);

当一个管道建立时,它会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而打开

例程

  1 #include<stdio.h>
  2 #include <unistd.h>
  3 #include<string.h>
  4 #include <stdlib.h>
  5 #include <sys/types.h>
  6 #include <sys/wait.h>
  7 int main()
  8 {
  9         int fd[2];
 10         int pid;
 11         char buff[128]={0};
 12     //  int pipe(int pipefd[2]);
 13         if(pipe(fd)==-1)
 14         {
 15         printf("pipe create failed!\n");
 16         }
 17         pid=fork();
 18         if(pid<0)
 19         {
 20         printf("create fork failed\n");
 21         }
 22         else if(pid>0)
 23         {
 24                 sleep(3);
 25         printf("this is father pid\n");
 26         close(fd[0]);
 27         write(fd[1],"Hello world",strlen("Hello world"));//这里只作为学习演示,就不判断返回值
 28         wait(NULL);
 29         }
 30         else
 31         {
 32         printf("this is child pid\n");
 33         close(fd[1]);
 34         read(fd[0],buff,128);//子进程读时,若父进程还没写则会阻塞在这里
 35         printf("%s\n",buff);
 36         exit(0);
 37         }
 38 
 39 
 40 
 41 return 0;
 42 }

运行结果

chen@chen-virtual-machine:~/IPC$ ./a.out
this is child pid
this is father pid
Hello world

二 FIFO

FIFO,也称为命名管道,它是一种文件类型。

1、特点
FIFO可以在无关的进程之间交换数据,与无名管道不同。

FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。

原型

 #include <sys/types.h>
 #include <sys/stat.h>
 int mkfifo(const char *pathname, mode_t mode);

例程
创建管道

  1 #include<stdio.h>
  2 #include <unistd.h>
  3 #include<string.h>
  4 #include <stdlib.h>
  5 #include <sys/types.h>
  6 #include <sys/wait.h>
  7 #include <sys/stat.h>
  8 #include <errno.h>
  9 
 10 int main()
 11 {
 12 //      int mkfifo(const char *pathname, mode_t mode);
 13 
 14     if((mkfifo("./file",0600)==-1)&&errno!=EEXIST)
 15     {
 16     printf("mkfifo create failed\n");
 17     perror("why");
 18     }
 19     else
 20     {
 21             if(errno==EEXIST)
 22             {
 23             printf("file have\n");
 24             }else{
 25     printf("mkfifo create sucess\n");
 26             }
 27     }
 28 
 29 
 30 
 31 
 32 return 0;
 33 }

write.c

  1 #include<stdio.h>
  2 #include <unistd.h>
  3 #include<string.h>
  4 #include <stdlib.h>
  5 #include <sys/types.h>
  6 #include <sys/wait.h>
  7 #include <sys/stat.h>
  8 #include <errno.h>
  9 #include <fcntl.h>
 10 int main()
 11 {
 12    int fd;
 13    int cnt=0;
 14    char *str="mkfifo data from write";
 15     fd=open("./file",O_WRONLY);
 16     while(1)
 17     {
 18     sleep(1);
 19     write(fd,str,strlen(str));
 20     cnt++;
 21     if(cnt==5)
 22     {
 23     break;
 24     }
 25     }
 26     close(fd);
 27 
 28 return 0;
 29 }

read.c

  1 #include<stdio.h>
  2 #include <unistd.h>
  3 #include<string.h>
  4 #include <stdlib.h>
  5 #include <sys/types.h>
  6 #include <sys/wait.h>
  7 #include <sys/stat.h>
  8 #include <errno.h>
  9 #include <fcntl.h>
 10 int main()
 11 {
 12 //      int mkfifo(const char *pathname, mode_t mode);
 13 
 14    char buff[50]={0};
 15     int fd;
 16     if((mkfifo("./file",0600)==-1)&&errno!=EEXIST)
 17     {
 18     printf("mkfifo create failed\n");
 19     perror("why");
 20     }
 21 
 22     fd=open("./file",O_RDONLY|O_NONBLOCK);//非阻塞方式打开 一般阻塞打开
 23     while(1)
 24     {
 25     read(fd,buff,50);
 26     sleep(1);
 27     printf("%s\n",buff);
 28     }
 29     close(fd);
 30 
 31 return 0;
 32 }

读端
chen@chen-virtual-machine:~/IPC$ ./r



mkfifo data from write
mkfifo data from write
mkfifo data from write
mkfifo data from write
mkfifo data from write
mkfifo data from write//读完5次 
mkfifo data from write
mkfifo data from write
mkfifo data from write
mkfifo data from write
mkfifo data from write
mkfifo data from write

三.消息队列

消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。

1、特点
消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。

消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。

消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。

原型

1 #include <sys/msg.h>
2 // 创建或打开消息队列:成功返回队列ID,失败返回-1
3 int msgget(key_t key, int flag);
4 // 添加消息:成功返回0,失败返回-1
5 int msgsnd(int msqid, const void *ptr, size_t size, int flag);
6 // 读取消息:成功返回消息数据的长度,失败返回-1
7 int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
8 // 控制消息队列:成功返回0,失败返回-1
9 int msgctl(int msqid, int cmd, struct msqid_ds *buf);

例程
msgread.c

  1 #include<stdio.h>
  2 #include <sys/types.h>
  3 #include <sys/ipc.h>
  4 #include <sys/msg.h>
  5 #include<string.h>
  6 //int msgget(key_t key, int msgflg);
  7 //int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
  8 //ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
  9  struct msgbuf {
 10                 long mtype;       /* message type, must be > 0 */
 11                 char mtext[128];    /* message data */
 12           };
 13 
 14 
 15 int main()
 16 {
 17         struct msgbuf readbuf;
 18         key_t key;
 19         key=ftok(".",'5');
 20         printf("key=%x\n",key);
 21         int msgID=msgget(key,IPC_CREAT|0777);
 22         if(msgID==-1)
 23         {
 24         printf("create msg failed\n");
 25         }
 26 
 27         msgrcv(msgID,&readbuf,sizeof(readbuf.mtext),888,0);
 28         printf("read from que:%s\n",readbuf.mtext);
 29         struct msgbuf sendbuf={988,"ok I gat it!!!!"};
 30         msgsnd(msgID,&sendbuf,strlen(sendbuf.mtext),0);
 31 
 32 
 33         msgctl(msgID,IPC_RMID,NULL);
 34 
 35 
 36 return 0;
 37 }

msgSend.c

  1 #include<stdio.h>
  2 #include <sys/types.h>
  3 #include <sys/ipc.h>
  4 #include <sys/msg.h>
  5 #include<string.h>
  6 
  7 //int msgget(key_t key, int msgflg);
  8 //int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
  9 //ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
 10  struct msgbuf {
 11                 long mtype;       /* message type, must be > 0 */
 12                 char mtext[128];    /* message data */
 13           };
 14 
 15 
 16 int main()
 17 {
 18         struct msgbuf sendbuf={888,"this is message from quen"};
 19         key_t key;
 20         key=ftok(".",'5');
 21         printf("key=%x\n",key);
 22         int msgID=msgget(key,IPC_CREAT|0777);//权限可读可写可执行
 23         if(msgID==-1)
 24         {
 25         printf("create msg failed\n");
 26         }
 27         struct msgbuf readbuf;
 28         msgsnd(msgID,&sendbuf,strlen(sendbuf.mtext),0);//0是阻塞方式
 29         msgrcv(msgID,&readbuf,sizeof(readbuf.mtext),988,0);//0是阻塞方式
 30         printf("msg from get:%s\n",readbuf.mtext);
 31 
 32         msgctl(msgID,IPC_RMID,NULL);//移除消息队列,IPC_RMID常用
 33 
 34 return 0;
 35 }

运行结果

Get 端
chen@chen-virtual-machine:~/IPC$ gcc msgGet.c -o G
chen@chen-virtual-machine:~/IPC$ ./G
key=350108bc
read from que:this is message from quen
Send 端
chen@chen-virtual-machine:~/IPC$ gcc msgSend.c -o S
chen@chen-virtual-machine:~/IPC$ ./S
key=350108bc
msg from get:ok I gat it!!!!

四.共享内存

共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。

1、特点
共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。

因为多个进程可以同时操作,所以需要进行同步。

信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问

原型

1 #include <sys/shm.h>
2 // 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
3 int shmget(key_t key, size_t size, int flag);
4 // 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
5 void *shmat(int shm_id, const void *addr, int flag);
6 // 断开与共享内存的连接:成功返回0,失败返回-1
7 int shmdt(void *addr); 
8 // 控制共享内存的相关信息:成功返回0,失败返回-1
9 int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

例程
shmw.c

  1 #include <sys/ipc.h>
  2 #include <sys/shm.h>
  3 #include<stdio.h>
  4 #include<string.h>
  5 #include<stdlib.h>
  6 //int shmget(key_t key, size_t size, int shmflg);
  7 int main()
  8 {
  9         int shmid;
 10         char *shmaddr;
 11         key_t key;
 12         key=ftok(".",'A');
 13         shmid=shmget(key,1024*4,IPC_CREAT|0666);//创建共享内存,内存大小必须以M为单位
 14         if(shmid==-1)
 15         {
 16         printf("create shmget failed\n");
 17         exit(-1);
 18         }
 19 //        exit(-1);
 20         shmaddr=shmat(shmid,0,0);//第二 0自动分配内存 第三 0可读可写 返回值共享内存指针
 21         printf("shmat ok\n");
 22         
 23         strcpy(shmaddr,"this is data from shmwrite\n");
 24         sleep(5);
 25         shmdt(shmaddr);//断开与共享内存的连接
 26         shmctl(shmid,IPC_RMID,0);//IPC_RMID 移除系统中的共享内存
 27         
 28         printf("quit\n");
 29         
 30         return 0;
 31 }    

shmr.c

  1 #include <sys/ipc.h>
  2 #include <sys/shm.h>
  3 #include<stdio.h>
  4 #include<string.h>
  5 #include<stdlib.h>
  6 #include <unistd.h>
  7 //int shmget(key_t key, size_t size, int shmflg);
  8 int main()
  9 {
 10         int shmid;
 11         char *shmaddr;
 12         key_t key;
 13         key=ftok(".",'A');
 14         shmid=shmget(key,1024*4,0);
 15         if(shmid==-1)
 16         {
 17         printf("create shmget failed\n");
 18         exit(-1);
 19         }
 20 
 21         shmaddr=shmat(shmid,0,0);
 22         printf("shmat ok\n");
 23         printf("data is :%s\n",shmaddr);
 24 
 25         shmdt(shmaddr);
 26 
 27         printf("quit\n");
 28 
 29         return 0;
 30 }

运行结果

读端
chen@chen-virtual-machine:~/IPC$ ./r
shmat ok
data is :this is data from shmwrite

quit
写端
chen@chen-virtual-machine:~/IPC$ ./w
shmat ok
quit

5.信号

信号概述
对于 Linux来说,实际信号是软中断,许多重要的程序都需要处理信号。信号,为 Linux 提供了一种处理异步事件的方法。比如,终端用户输入了 ctrl+c 来中断程序,会通过信号机制停止一个程序。

信号概述
信号的名字和编号:
每个信号都有一个名字和编号,这些名字都以“SIG”开头,例如“SIGIO ”、“SIGCHLD”等等。
信号定义在signal.h头文件中,信号名都定义为正整数。
具体的信号名称可以使用kill -l来查看信号的名字以及序号,信号是从1开始编号的,不存在0号信号。kill对于信号0又特殊的应用。
linux的信号

chen@chen-virtual-machine:~/IPC$ kill -l
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX	

信号的处理:
信号的处理有三种方法,分别是:忽略、捕捉和默认动作

忽略信号,大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(分别是 SIGKILL和SIGSTOP)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程,显然是内核设计者不希望看到的场景
捕捉信号,需要告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号处理函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。
系统默认动作,对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。不过,对系统来说,大部分的处理方式都比较粗暴,就是直接杀死该进程。
具体的信号默认动作可以使用man 7 signal来查看系统的具体定义。在此,我就不详细展开了,需要查看的,可以自行查看。也可以参考 《UNIX 环境高级编程(第三部)》的 P251——P256中间对于每个信号有详细的说明。
了解了信号的概述,那么,信号是如何来使用呢?

其实对于常用的 kill 命令就是一个发送信号的工具,kill 9 PID来杀死进程。比如,我在后台运行了一个 top 工具,通过 ps 命令可以查看他的 PID,通过 kill 9 来发送了一个终止进程的信号来结束了 top 进程。如果查看信号编号和名称,可以发现9对应的是 9) SIGKILL,正是杀死该进程的信号。而以下的执行过程实际也就是执行了9号信号的默认动作——杀死进程。


chen@chen-virtual-machine:~/IPC$ ps -aux|grep read
root          2  0.0  0.0      0     0 ?        S    11:47   0:00 [kthreadd]
chen       7136  0.0  0.0   4220   652 pts/20   S+   18:54   0:00 ./read
chen       7141  0.0  0.0  15984   928 pts/2    S+   18:54   0:00 grep --color=auto read
chen@chen-virtual-machine:~/IPC$ kill -9 7136

chen@chen-virtual-machine:~/IPC$ ./read
已杀死

信号处理 signal
原型

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

例程

  1 #include<stdio.h>
  2 #include <signal.h>
  3 //typedef void (*sighandler_t)(int);
  4 //sighandler_t signal(int signum, sighandler_t handler);
  5 
  6 void handler(int signum)
  7 {
  8         printf("get signal %d\n",signum);
  9         switch(signum){
 10                 case 2:printf("SIGINT\n");break;
 11                 case 9:printf("SIGKILL\n");break;
 12                 case 10:printf("SIGUSR1\n");break;
 13 
 14         }
 15         printf("never quit\n");
 16 
 17 }
 18 int main()
 19 {
 20         signal(SIGINT,handler);
 21         signal(SIGKILL,handler);
 22         signal(SIGUSR1,handler);
 23         while(1);
 24 
 25 
 26 return 0;
 27 }


这里kill -9 信号没有执行handler函数

输入 信号 与 PID 传递信号到进程

  1 #include <stdio.h>
  2 #include <signal.h>
  3 #include <sys/types.h>
  4 #include <stdlib.h>
  5 
  6 int main(int argc,char **argv)
  7 {
  8         int signum;
  9 
 10         int  pid;
 11         char cmd[128]={0};
 12         signum = atoi(argv[1]);//将文本数字转为数字
 13         pid = atoi(argv[2]);
 14 
 15         printf("signum: %d,pid: %d\n",signum,pid);
 16 
 17     //  kill(pid,signum);
 18         sprintf(cmd,"kill -%d %d",signum,pid);
 19         system(cmd);
 20         printf("kill\n");
 21 
 22 return 0;
 23 }

在这里插入图片描述
信号忽略

signal(SIGINT,SIG_IGN)//可以忽略掉ctrl c 的信号

信号如何携带信息
高级版signal ——sigaction

#include <signal.h>
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

高级版的发送 sigqueue

 #include <signal.h>
 int sigqueue(pid_t pid, int sig, const union sigval value);

收信号

  1 #include<stdio.h>
  2 #include <signal.h>
  3 #include <sys/types.h>
  4 #include <unistd.h>
  5 
  6 //int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
  7 
  8 void handler(int signum, siginfo_t *info,void *context)//info里面的参数可以通过man手册查询
  9 {
 10         printf("get signal%d \n",signum);
 11         if (context !=NULL)
 12         {
 13         printf("get data%d\n",info->si_int);
 14         printf("get data%d\n",info->si_value.sival_int);//与上面不一样的方式,一样的数据
 15         printf("from %d\n",info->si_pid);//发送者pid
 16         }
 17 
 18 
 19 }
 20 
 21 int main()
 22 {
 23         struct sigaction act;
 24         printf("pid=%d\n",getpid());
 25         act.sa_sigaction=handler;
 26         act.sa_flags=SA_SIGINFO;//能接收消息的宏
 27 
 28         sigaction(SIGUSR1,&act,NULL);//参数1信号 参数二结构体 参数3备份
 29         while(1);
 30 
 31 return 0;
 32 }

发信号

  1 #include<stdio.h>
  2 #include <signal.h>
  3 #include <stdlib.h>
  4 #include <sys/types.h>
  5 #include <unistd.h>
  6 
  7 //int sigqueue(pid_t pid, int sig, const union sigval value);
  8 
  9 int main(int argc,char **argv)
 10 {
 11         int signum;
 12         int pid;
 13 
 14         signum=atoi(argv[1]);
 15         pid=atoi(argv[2]);
 16 
 17         union sigval value;
 18         value.sival_int=100;
 19 
 20         sigqueue(pid,signum,value);
 21         printf("over mypid is %d\n",getpid());
 22 
 23 
 24 return 0;
 25 }
           /* union sigval {
               int   sival_int;
               void *sival_ptr;
                         };*/

结构体原型

struct sigaction {
   void       (*sa_handler)(int); //信号处理程序,不接受额外数据,SIG_IGN 为忽略,SIG_DFL 为默认动作
   void       (*sa_sigaction)(int, siginfo_t *, void *); //信号处理程序,能够接受额外数据和sigqueue配合使用
   sigset_t   sa_mask;//阻塞关键字的信号集,可以再调用捕捉函数之前,把信号添加到信号阻塞字,信号捕捉函数返回之前恢复为原先的值。
   int        sa_flags;//影响信号的行为SA_SIGINFO表示能够接受数据
 };
//回调函数句柄sa_handler、sa_sigaction只能任选其一
             siginfo_t {
               int      si_signo;     /* Signal number */
               int      si_errno;     /* An errno value */
               int      si_code;      /* Signal code */
               int      si_trapno;    /* Trap number that caused
                                         hardware-generated signal
                                         (unused on most architectures) */
               pid_t    si_pid;       /* Sending process ID */
               uid_t    si_uid;       /* Real user ID of sending process */
               int      si_status;    /* Exit value or signal */
               clock_t  si_utime;     /* User time consumed */
               clock_t  si_stime;     /* System time consumed */
               sigval_t si_value;     /* Signal value */
               int      si_int;       /* POSIX.1b signal */
               void    *si_ptr;       /* POSIX.1b signal */
               int      si_overrun;   /* Timer overrun count;
                                         POSIX.1b timers */
               int      si_timerid;   /* Timer ID; POSIX.1b timers */
               void    *si_addr;      /* Memory location which caused fault */
               long     si_band;      /* Band event (was int in
                                         glibc 2.3.2 and earlier) */
               int      si_fd;        /* File descriptor */
               short    si_addr_lsb;  /* Least significant bit of address
                                         (since Linux 2.6.32) */
               void    *si_call_addr; /* Address of system call instruction
                                         (since Linux 3.5) */
               int      si_syscall;   /* Number of attempted system call
                                         (since Linux 3.5) */
               unsigned int si_arch;  /* Architecture of attempted system call
                                         (since Linux 3.5) */
           }

6.信号量

信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。

1、特点
信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。

信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。

每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。

支持信号量组。

2、原型

最简单的信号量是只能取 0 和 1 的变量,这也是信号量最常见的一种形式,叫做二值信号量(Binary Semaphore)。而可以取多个正整数的信号量被称为通用信号量。

Linux 下的信号量函数都是在通用的信号量数组上进行操作,而不是在一个单一的二值信号量上进行操作。

1 #include <sys/sem.h>
2 // 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
3 int semget(key_t key, int num_sems, int sem_flags);
4 // 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
5 int semop(int semid, struct sembuf semoparray[], size_t numops);  
6 // 控制信号量的相关信息
7 int semctl(int semid, int sem_num, int cmd, ...);

当semget创建新的信号量集合时,必须指定集合中信号量的个数(即num_sems),通常为1; 如果是引用一个现有的集合,则将num_sems指定为 0 。

在semop函数中,sembuf结构的定义如下:

1 struct sembuf 
2 {
3     short sem_num; // 信号量组中对应的序号,0~sem_nums-1
4     short sem_op;  // 信号量值在一次操作中的改变量
5     short sem_flg; // IPC_NOWAIT, SEM_UNDO
6 }

例程

#include<stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>       
//int semget(key_t key, int nsems, int semflg);
             //            几个信号量    创建 和权限
 union semun {
		int  val;    /* Value for SETVAL */
		struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
		unsigned short  *array;  /* Array for GETALL, SETALL */
	    struct seminfo  *__buf;  /* Buffer for IPC_INFO
								 (Linux-specific) */
};
//int semop(int semid, struct sembuf *sops, size_t nsops);对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1

void pGetKey(int id)//p操作 拿锁
{
		struct sembuf set;
		set.sem_num=0;//信号量编号,默认0
		set.sem_op=-1 ;//锁-1
		set.sem_flg=SEM_UNDO;//进程终止取消对锁操作
  semop(id,&set ,1);//1代表第二个参数的个数
  printf("get key\n");
}

void vPutBackKey(int id)
{
		struct sembuf set;
		set.sem_num=0;
		set.sem_op=1 ;//锁加1
		set.sem_flg=SEM_UNDO;
  semop(id,&set ,1);
  printf("put back key\n");
}

int main()
{
		key_t key;
		pid_t pid;
        

		int semid;
		key=ftok(".",11);
		semid=semget(key,1,IPC_CREAT|0666);1//信号量组中有几个信号量我这边选择1个,IPC_CREAT|0666:如果有就获取没有就创建信号量组,权限可读可写可执行

        union semun initsem;
        initsem.val=0;//现在没有锁
        //int semctl(int semid, int sem_num, int cmd, ...); 控制信号量的相关信息
	    semctl(semid,0,SETVAL,initsem);//初始化信号量 int sem_num是代表操作第几个信号量,我这边写0代表操作第一个信号量
		//0表示操作第0个型号量 SETVAL 设置信号量的值 设置为initsem

		pid=fork();
		if(pid>0)
		{
				pGetKey(semid);//父进程拿锁,开始没有锁所以不会先运行
		printf("this is father\n");
		vPutBackKey(semid);//放回锁
		semctl(semid,0,IPC_RMID);
		}else if(pid==0)
		{
		printf("this is child\n");
		vPutBackKey(semid);//子进程放回锁
		}

return 0;
}
             

运行结果

chen@chen-virtual-machine:~/IPC$ gcc semget.c
chen@chen-virtual-machine:~/IPC$ ./a.out
this is child
put back key
get key
this is father
put back key
chen@chen-virtual-machine:~/IPC$ ./a.out
this is child
put back key
get key
this is father
put back key
chen@chen-virtual-machine:~/IPC$ ./a.out//每次都是子进程先运行
this is child
put back key
get key
this is father
put back key

消息队列 共享内存 信号量 结合例程

这个例子使用了【共享内存+信号量+消息队列】的组合来实现服务器进程与客户进程间的通信。

共享内存用来传递数据;
信号量用来同步;
消息队列用来 在客户端修改了共享内存后 通知服务器读取。
server.c

#include<stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/msg.h>
#include<string.h>
#include <stdlib.h>
#include <errno.h>
union semun {// 联合体,用于semctl初始化
		    int val;    /* Value for SETVAL */
			struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
			unsigned short  *array;  /* Array for GETALL, SETALL */
			struct seminfo  *__buf;  /* Buffer for IPC_INFO	
			                         (Linux-specific) */											                          
};


struct msgbuf {
		  long mtype;       /* message type, must be > 0 */
		  char mtext;    /* message data */
};

void semkey_p(int id)//拿锁
{
		struct sembuf set;
		set.sem_num=0;
		set.sem_op=-1;
		set.sem_flg=SEM_UNDO;
		//int semop(int semid, struct sembuf *sops, size_t nsops);
		if(semop(id,&set,1)==-1)
		{
		perror("why");
		printf("semop p failed");
		exit(-1);
		}
		printf("get key\n");
}

void semkey_v(int id)//放回锁
{
        struct sembuf set;
		set.sem_num=0;
		set.sem_op=1;
		set.sem_flg=SEM_UNDO;
		if(semop(id,&set,1)==-1)
		{
		perror("why");
		printf("semop v failed");
		exit(-1);
		}
		printf("back key\n");

}

int main()
{
		int shmid;
		key_t key,key1;
		char *shmaddr;//共享内存地址

		int semid;//创建信号量返回id

		struct msgbuf readbuf;//读取消息队列结构体
		int msgid;//消息队列返回ID
		
        //int shmget(key_t key, size_t size, int shmflg);
		key=ftok(".",2);
		key1=ftok(".",3);
		shmid=shmget(key,1024,IPC_CREAT|0666);//创建共享内存1M 权限可读可写
        if(shmid==-1)
		{
		perror("why:");
		printf("cretae shmget failed\n");
		exit(-1);
		}

	    //void *shmat(int shmid, const void *shmaddr, int shmflg);
		shmaddr=shmat(shmid,0,0);//连接共享内存
	//	if((int)shmaddr==-1)
	//	{
	//	perror("why");
	//	printf("create shmddr failed\n");
	//	exit(-1);
	//	}

        //int semget(key_t key, int nsems, int semflg);
		semid=semget(key,1,IPC_CREAT|0666);//信号量创建
		if(semid==-1)
		{
		printf("create semget failed\n");
		perror("why");
		exit(-1);
		}
		union semun initsem;//初始化信号量联合体
		initsem.val=1;//初始有一把锁
		//int semctl(int semid, int semnum, int cmd, ...);
		if(semctl(semid,0,SETVAL,initsem)==-1)//初始化信号量
		{
		perror("why");
		printf("semctl failed\n");
		exit(-1);
		}

        //int msgget(key_t key, int msgflg);
        msgid=msgget(key1,IPC_CREAT|0777);//创建消息队列
				if(msgid==-1)
		{
		printf("msgget failed\n");
		perror("why");
		exit(-1);
        } 
		printf("DDDD\n");
        while(1)
		{
        //ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
		msgrcv(msgid,&readbuf,1,888,0);//获取消息队列消息
		printf("readbuf=%c\n",readbuf.mtext);
		if(readbuf.mtext=='q')
		{
				break;
		
		}
		if(readbuf.mtext=='r')
		{
				semkey_p(semid);
				printf("%s\n",shmaddr);//服务器接收到客户端的消息
				semkey_v(semid);
		
		
		}
		
		

		}

		shmdt(shmaddr);//端口共享内存
		shmctl(shmid,IPC_RMID,0);//移除共享内存

		msgctl(msgid,IPC_RMID,NULL);//关闭消息队列

        semctl(semid,0,IPC_RMID);//关闭信号量

		return 0;
}

client.c

#include<stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/msg.h>
#include<string.h>
#include <stdlib.h>
#include <errno.h>
union semun {
		    int val;    /* Value for SETVAL */
			struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
			unsigned short  *array;  /* Array for GETALL, SETALL */
			struct seminfo  *__buf;  /* Buffer for IPC_INFO												                         (Linux-specific) */
};


struct msgbuf {
		  long mtype;       /* message type, must be > 0 */
		  char mtext;    /* message data */
};

void semkey_p(int id)//拿锁
{
		struct sembuf set;
		set.sem_num=0;
		set.sem_op=-1;
		set.sem_flg=SEM_UNDO;
		//int semop(int semid, struct sembuf *sops, size_t nsops);
		if(semop(id,&set,1)==-1)
		{
		perror("why");
		printf("semop p failed");
		exit(-1);
		}
		printf("get key\n");
}

void semkey_v(int id)//放锁
{
        struct sembuf set;
		set.sem_num=0;
		set.sem_op=1;
		set.sem_flg=SEM_UNDO;
		if(semop(id,&set,1)==-1)
		{
		perror("why");
		printf("semop v failed");
		exit(-1);
		}
		printf("back key\n");

}

int main()
{
		int shmid;
		key_t key,key1;
		char *shmaddr;

		int semid;

		struct msgbuf sendbuf;
		int msgid;

		char tmp;

		key=ftok(".",2);
		key1=ftok(".",3);
        //int shmget(key_t key, size_t size, int shmflg);
		shmid=shmget(key,1024,0);//获取共享内存
        if(shmid==-1)
		{
		perror("why:");
		printf("cretae shmget failed\n");
		exit(-1);
		}

	    //void *shmat(int shmid, const void *shmaddr, int shmflg);
		shmaddr=shmat(shmid,0,0);//连接共享内存
	//	if((int)shmaddr==-1)
	//	{
	//	perror("why");
	//	printf("create shmddr failed\n");
	//	exit(-1);
	//	}

        //int semget(key_t key, int nsems, int semflg);
		semid=semget(key,0,0);//获取信号量
		if(semid==-1)
		{
		printf("create semget failed\n");
		perror("why");
		exit(-1);
		}
		
        //int msgget(key_t key, int msgflg);
        msgid=msgget(key1,0);//获取消息队列
		if(msgid==-1)
		{
		printf("msgget failed\n");
		perror("why");
		exit(-1);
		}
 
        while(1)
		{char c;
				printf("please input command by 'r' or 'q'\n");
				scanf("%c",&c);//输入读操作还是退出
				switch(c)
				{
						case 'r':printf("input data\n");
								 semkey_p(semid);
								 scanf("%s",shmaddr);//输入消息到共享内存
								  while((c=getchar())!='\n' && c!=EOF);//吃掉多余输入
								 semkey_v(semid);
								 sendbuf.mtype=888;
								 sendbuf.mtext='r';
								 msgsnd(msgid,&sendbuf,sizeof(sendbuf.mtext),0);
								 break;
				        case 'q':sendbuf.mtype=888;
								 sendbuf.mtext='q';
								 shmdt(shmaddr);//断开共享内存
								 msgsnd(msgid,&sendbuf,sizeof(sendbuf.mtext),0);//消息队列发送退出消息
								 return 0;
								 break;
						default:printf("wrong input");
								while((c=getchar())!='\n' && c!=EOF);//吃掉多余输入
				}
		
		}

		return 0;
}

运行结果

在这里插入图片描述

7.总结

1.管道:速度慢,容量有限,只有父子进程能通讯

2.FIFO:任何进程间都能通讯,但速度慢

3.消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题

4.信号量:不能传递复杂消息,只能用来同步

5.共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值