进程间通信(IPC(InterProcess Communication))
在上一节进程中我们提到了,等待进程的机制是老张隔一段时间去厨房看一下水有没有烧开(非阻塞同步)。
跟站在旁边等水开(阻塞同步)相比,这样做有优势,但是仍然浪费了很多时间和资源。最好是买个能发出信号的水壶。
水开时通知老张,也就是需要进程间的通信。
进程间有几种经典通讯方式:管道、信号、消息队列、信号量、共享内存。
无名管道(匿名管道)
在linux系统中,一切皆是文件。管道、也是一种文件。因为是无名管道,也就是没有名字,没有文件描述符。
和普通文件不同的是,无名管道不占用内存。
管道是半双工的通讯方式,类似于stm32中的串口。我们这里复习一下半双工的概念:
单工:只支持单向的数据传输。比如电视 广播
半双工:同一时间,只能有一个方向的数据传输。比如对讲机
全双工:同一时间,可以互相发送数据,实现同时收发的功能。比如电话
这里介绍的管道是半双工通讯。同一时间,只能由一个进程写,另一个进程读。
(那么有没有全双工的管道呢?有。我们叫他流管道,后面有机会再说)
使用管道之前我们要创建一个管道,前面说过管道也是一个文件。那么创建管道就和创建文件类似
int pipe( int fd[2] );
fd[2] 其实就是前面文件iO中的文件描述符,这里的表达形式表示:
能且只能返回2个参数,分别是 fd[0] 和 fd[1],这两个文件描述符分别用来读、写。
成功返回0,失败返回 -1 .(所以可以用perror()了)
除了创建之外,读写关闭都可以用文件IO中的函数操作。
我们使用这个函数创建一个管道:
#include <unistd.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> int main() { int pipe_fd[2]; /*创建一无名管道*/ if(pipe(pipe_fd)<0) { perror("pipe create error"); return -1; } else { printf("pipe create success "); } /*关闭管道描述符*/ close(pipe_fd[0]); close(pipe_fd[1]); }
那么如何使用这个管道?
创建一个管道的目的,自然是让父子进程都可以使用。也就是说让父子进程共用一个管道
这样才能保证他们之间的通信,那么就需要 pipe() 在 fork() 之前。否则子进程创建自己的管道他们之间信息传递不了
然后关掉父进程的读端fd[0] 子进程的写端fd[1]
变成了这个样子:
这样就达到了一个父进程写,子进程读的目的。
看下面的例子:
#include <unistd.h> #include <sys/types.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/wait.h> int main() { int pipe_fd[2]; pid_t pid; char buf_r[100]; char* p_wbuf; int r_num; memset(buf_r,0,sizeof(buf_r)); /*创建管道*/ if(pipe(pipe_fd)<0) { perror("pipe create error"); return -1; } /*创建一子进程*/ if((pid=fork())==0) { printf(" "); /*关闭子进程写描述符,并通过使父进程暂停 2 秒确保父进程已关闭相应的读描述符*/ close(pipe_fd[1]); sleep(2); /*子进程读取管道内容*/ if((r_num=read(pipe_fd[0],buf_r,100))>0) { printf("%d numbers read from the pipe is %s ",r_num,buf_r); } /*关闭子进程读描述符*/ close(pipe_fd[0]); exit(0); } else if(pid>0) { /*/关闭父进程读描述符,并分两次向管道中写入 Hello Pipe*/ close(pipe_fd[0]); if(write(pipe_fd[1],"Hello",5)!= -1) printf("parent write1 success! "); if(write(pipe_fd[1]," Pipe",5)!= -1) printf("parent write2 success! "); /*关闭父进程写描述符*/ close(pipe_fd[1]); sleep(3); /*收集子进程退出信息*/ waitpid(pid,NULL,0); exit(0); } }
在fork()之后,父子进程分别关闭对应的读写端,使父进程只写 子进程只读
然后父进程写入 Hello Pipe,子进程读并打印,接下来分别关闭管道,父进程收尸退出。
因为无名管道的创建方式决定了,他只能用于有亲缘关系之间的进程通信。
向管道中写入数据时,linux 将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读取管道缓冲区中的数据,那么写操作将会一直阻塞。
标准流管道
有这么一个函数,同时包含创建 打开 并启动另外一个进程。
FILE *popen(const char *command, const char *type)
command 一个shell命令
type r/w 分别表示输出/输入
成功返回0,失败返回-1
由popen创建的流管道必须由 pclose() 关闭。
int pclose(FILE *stream)
先用popen创建一个进程"ls -l",把内容写进新建的文件shell.c 中。
#include<stdio.h> #include<unistd.h> #include<string.h> int main() { FILE *fp=NULL; FILE *fh=NULL; char buff[1024]={0}; memset(buff,0,sizeof(buff)); fp=popen("ls -l","r");//将命令ls-l 同过管道读到fp fh=fopen("shell.c","w+");// 创建一个可写的文件 fread(buff,1,1024,fp);//将fp的数据流读到buff中 fwrite(buff,1,1024,fh);//将buff的数据写入fh指向的文件中 pclose(fp); fclose(fh); return 0; }
执行结果如下:
命名管道(有名管道)
上面提到的无名管道只能进行有亲缘关系的通信,那么命名管道就是解决无亲缘关系的进程之间通信的问题
也就是说 在同一个电脑上的进程,都可以通过命名管道通信。
和无名管道类似,命名管道也是文件,所以开关读写的文件IO操作同样可作用于此文件。
创建方法如下:
#include <sys/types.h> #include <sys/stat.h> int mkfifo(const char *pathname, mode_t mode);
int mknod(const char *pathname, mode_t mode, dev_t dev);
mkfifo是POSIX.1首先提出的。SVR3用mknod(2)系统调用创建FIFO。
而在SVR4中,mkfifo调用mknod创建FIFO。
POSIX.2已经建议了一个mkfifo(1)命令。SVR4和4.3+BSD现在支持此命令。
于是,用一条shell命令就可以创建一个FIFO,然后用一般的shell I/O重新定向对其进行存取。
以上摘自《UNIX环境高级编程》。下文中我们使用mkfifo()函数。
很类似我们的open函数,第一个参数是文件名,第二个参数mode是创建模式。dev是设备值,只有创建设备文件时会用到,一般填0。
mode参数可选值如下:
O_RDONLY:读管道
O_WRONLY:写管道
O_RDWR:读写管道
O_NONBLOCK:非阻塞
O_CREAT:如果该文件不存在,那么就创建一个新的文件,并用 第三的参数为其设置权限
O_EXCL:如果使用 O_CREAT 时文件存在,那么可返回错误消息。这一参数可测试文件是否存在
错误信息:
EACCESS 参数 filename 所指定的目录路径无可执行的权限
EEXIST 参数 filename 所指定的文件已存在
ENAMETOOLONG 参数 filename 的路径名称太长
ENOENT 参数 filename 包含的目录不存在
ENOSPC 文件系统的剩余空间不足
ENOTDIR 参数 filename 路径中的目录存在但却非真正的目录
EROFS 参数 filename 指定的文件存在于只读文件系统内
也可以从命令行直接创建管道
$ mkfifo myfifo
这样就创建了一个叫myfifo的有名管道
普通的文件操作:开关读写。
其中开open()和读read(),会阻塞运行函数。
当一个进程使用open()以只写的方式打开管道文件,程序会阻塞运行。等待另一个进程以只读方式打开该管道。
这个例子中我遇到了一个坑:
#include <stdio.h> #include <unistd.h> #include <strings.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #define FIFO_NAME "/mnt/test/testfifo" int main(int argc, char* argv[]) { int fd; char buf[5] = "hello"; int ret ; ret = mkfifo(FIFO_NAME, 0777); if(ret == -1) { perror("mkfifo faild"); } fd = open(FIFO_NAME, O_WRONLY); write(fd, buf, strlen(buf)+1); close(fd); unlink(FIFO_NAME);//删除管道文件 sleep(1); return 0; }
#include <stdio.h> #include <unistd.h> #include <strings.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #define FIFO_NAME "/mnt/test/testfifo" #define BUF_SIZE 1024 int main(int argc, char* argv[]) { int fd; char buf[BUF_SIZE]; memset(buf,0x31,5); fd = open(FIFO_NAME, O_RDONLY); if(fd < 0) { perror("open fifofile faild"); } read(fd, buf, BUF_SIZE); puts(buf); close(fd); return 0; }
其实很简单的程序,就是一个进程创建管道并且写入hello,然后另一个进程读hell并打印。
但是,我执行 ./fifoa 之后
我不信邪
所以又用上面提到的shell命令: mkfifo myfifo 试了一下
对,他就是把刚才perror返回给我的错误信息翻译了一遍。
这是文件权限的问题,所以我查看了我share的权限:
三个rwx,root权限也没有毛病,那么问题出在哪里呢?
其实是虚拟机和windows共享文件夹这里,
共享文件夹没有写权限,所以导致无法创建fifo文件。我在mnt/下面mkdir了一个新文件夹
这样再回去执行测试代码就可以正常通过了
这张图可以看出来右边终端,当我执行 ./fifo 之后因为open打开方式是只写,所以进入阻塞状态,等待另一个只读方式的open
此时我在左边终端,去刚才新建的test文件查看,就找到了刚才创建的testfifo 管道文件。
接下来再执行 ./fifob,只读方式打开了管道,所以管道接通,hello 字符串发送过来成功打印。
信号
老张的水壶在水开的时候会响,就是发送了一个信号给老张,老张接收了这个信号然后去执行下一步操作。
信号就类似于单片机中的中断,更类似于我们前面学习QT中的信号。
Linux内核中提供了一些可供我们使用的信号 kill -l 查看,这些定义好的信号都是>0正整数
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
就像水开了的警报一样,老张可以接收这个信号,也可以忽略这个信号,上表中的信号也是同理
但是有2个信号除外,就是9号和19号信号,SIGSTOP SIGKILL,这两个信号不可被忽略。
发送信号
发送信号函数kill,和刚才的那个shell命令相同,发送的就是上表中的信号之一
#include <sys/types.h> #include <signal.h> int kill(pid_t pid, int sig);
既然是发送信号,那么我们肯定要想好 发给谁? 发什么?
第一个参数 pid,就是接收信号的进程
第二个参数 sig,就是要发送的信号
成功返回0,失败返回-1
看下面的例子,当迭代的值为20时 自杀。
#include <sys/types.h> #include <signal.h> #include <unistd.h> #include <stdio.h> int main(int argc, char const *argv[]) { int i = 0; while(1) { i++; sleep(1); if (i == 20) { kill(getpid(),SIGKILL); } printf("propess num is : %d ",i); } return 0; }
运行结果如下:
系统调用的定时函数,闹钟函数
#include <unistd.h> unsigned int alarm(unsigned int seconds);
alarm只有一个参数,秒。
当到达这个时间时,就会向系统发送一个SIGALRM信号。
alarm和sleep不同,alarm会立即返回一个值。
要注意:一个进程只能使用一次alarm,如果多次使用后面的会覆盖前面的alarm
pause()将进程挂起直到捕捉到信号为止
#include <unistd.h> int pause(void);
例子:(在第五秒的时候发送时钟信号并打印hello)
#include <unistd.h> #include <signal.h> #include <stdio.h> void handler() { printf("hello "); } int main() { int i; signal(SIGALRM, handler); alarm(5); for(i = 1; i < 7; i++) { printf("sleep %d ... ", i); sleep(1); } }
运行结果如下:
信号的处理
#include <signal.h> void (*signal(int signum, void (*handler)(int)))(int)
该函数原型比较难懂,但是参数很好理解,既然是处理信号,那么参数无非就是处理什么信号,怎么处理信号
signum 就是要处理的信号
handler 处理方式 可以自己定义函数,也可以使用默认的定义
SIG_IGN 忽略该信号
SIG_DFL 系统默认方式处理
直接看一个例子
#include <signal.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> /*自定义信号处理函数*/ void my_func(int sign_no) { if(sign_no==SIGINT) printf("I have get SIGINT "); else if(sign_no==SIGQUIT) printf("I have get SIGQUIT "); } int main() { printf("my pid :%d ", getpid()); printf("Waiting for signal SIGINT or SIGQUIT "); /*发出相应的信号,并跳转到信号处理函数处*/ signal(SIGINT, my_func); signal(SIGQUIT, my_func); pause(); exit(0); }
当我在终端输入ctrl+c时,触发信号打印句子。
再看一个示例:
#include <signal.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> /*自定义信号处理函数*/ void my_func(int sign_no) { if(sign_no==SIGUSR1) { printf("I have get SIGUSR1 "); kill(getpid(),9); } } int main() { int fd; int i; fd = fork(); if(fd == -1) { perror("creat fork error"); exit(-1); } else if(fd == 0) { //child while(1) { i +=2; printf("child : %d ", i); signal(SIGUSR1,my_func); sleep(1); } } else { //father while(1) { i++; printf("father : %d ", i); if(i == 4) { kill(fd,SIGUSR1); } sleep(1); } } exit(0); }
父子进程同时开始计数,在父进程计第四个数得时候发给子进程一个信号,子进程接到信号后自杀
观测结果如下:
还有一个更高级的信号处理函数,用来替代signal
#include <signal.h> int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
参数:
signum 信号,除了SIGKIILL SIGSTOP
后两个参数都是结构体sigaction:
struct sigaction { void (*sa_handler)(int); void (*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask; int sa_flags; void (*sa_restorer)(void); };
画一个图解释一下这些成员
那么我们使用一下
用这个函数完成刚才的效果
#include <signal.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> /*自定义信号处理函数*/ void my_func(int sign_no) { if(sign_no==SIGUSR1) { printf("I have get SIGUSR1 "); kill(getpid(),9); } } int main() { int fd; int i = 0; fd = fork(); if(fd == -1) { perror("creat fork error"); exit(-1); } else if(fd == 0) { //child struct sigaction act; //结构体sigaction act.sa_handler = my_func; //信号处理函数 act.sa_flags = SA_RESETHAND; //信号处理之后恢复默认的处理方式 sigemptyset(&act.sa_mask); //清空屏蔽信号集 sigaction(SIGUSR1,&act,NULL); //信号接收 while(1) { i +=2; printf("child : %d ", i); sleep(1); } } else { //father while(1) { i++; printf("father : %d ", i); if(i == 4) { kill(fd,SIGUSR1); } sleep(1); } } exit(0); }
在 Linux 中,当一个信号的信号处理函数执行时,如果进程又接收到了该信号,该信号会自动被储存而不会中断信号处理函数的执行,直到信号处理函数执行完毕再重新调用相应的处理函数。
如果两个信号同时产生,系统并不能保证他们的调用次序。
共享内存
就是让两个进程使用同一块内存空间,这样实现信息的传递
可以使用shell命令查看IPC的状态
可以分别加 参数 -q -m -s来查看消息队列共享内存和信号量
两步走:一、创建共享内存 shmget
二、映射共享内存 shmat
在使用结束后,要撤销申请的内存 shmdt
创建共享内存:
#include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, size_t size, int shmflg);
key 键,就是上面截图中的【键】,不同进程可以通过这个键打开同一块共享内存
size 就是上面截图中的【字节】,你申请共享内存空间的大小
shmflg 申请新内存则填 IPC_CREAT 同时赋予权限
而如何选择第一个参数key 键值呢?
有三种方法
1、直接赋予一个值,但是该值不能与系统中其他存在的键值冲突
2、传入参数 IPC_PRIVATE,由系统自动分配一个键值
3、使用ftok生成一个键值
#include <sys/types.h> #include <sys/ipc.h> key_t ftok(const char *pathname, int proj_id);
pathname 路径名,此文件必须存在而且可读写 (随便一个什么文件都可以,只是根据文件信息和proj_id合成key)
proj_id 工程ID 自己定义 0-255
成功返回键值,失败返回 -1
#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <stdio.h> int main(void) { int shmid; key_t key; key = ftok("./test.c",1); printf("key = %d ",key); shmid = shmget(key,4096,IPC_CREAT|0600); printf("shmid = %d ",shmid); }
本例使用test.c 生成了一个key值,然后用shmget创建了一块大小为4k的共享内存,并打印shmid
可以看到已经申请了一块4k的内存空间,权限和shmid也对得上。
可以使用这个命令删除该内存
ipcrm -m 22
映射共享内存到进 #include <sys/types.h>
#include <sys/types.h> #include <sys/shm.h> void *shmat(int shmid, const void *shmaddr, int shmflg);
shmid 即刚才创建的shmid
shmaddr 在进程中映射地址,一般填NULL系统自动分配
shmflg 操作方式 0 可读可写 或者SHM_RDONLY 只读
成功返回映射区的地址,失败返回-1
取消内存映射
#include <sys/types.h> #include <sys/shm.h> int shmdt(const void *shmaddr);
shmaddr 映射地址
看个例子:申请一块内存空间,写入一句话,然后开另一个进程读这句话
#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <stdio.h> #include <string.h> int main(int argc ,char argv[]) { int shmid; key_t key; char *buf = "hello mmm"; char *p; key = ftok("./test.c",1); printf("key = %d ",key); shmid = shmget(key,4096,IPC_CREAT|0600); printf("shmid = %d ",shmid); p = shmat(shmid,NULL,0); strcpy(p,buf); return 0; }
#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <stdio.h> #include <string.h> #include <stdio.h> #include <stdlib.h> int main(int argc ,char argv[]) { int shmid; key_t key; char *buf = NULL; buf = shmat(31,NULL,0); puts(buf); return 0; }
输出结果如下:
因为我先运行了创建内存的进程,所以直接将shmid写入了读取内存的进程代码中。
消息队列
就是一个消息列表,用户可以从消息队列中添加 读取消息/。
基本操作有几种:创建/打开消息队列msgget、添加消息msgsnd、读取消息msgrcv、控制消息队列msgctl
消息队列和共享内存相比,消息队列可以取走指定的某一条消息。
创建/打开消息队列
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgget(key_t key, int msgflg);
key 键值,和上面共享内存的用法相同,可以使用IPC_PRIVATE 或者用ftok()创建一个
msgflg IPC_CREAT| 权限 比如0600
成功返回消息队列的ID(msqid),失败返回-1
试用一下:
int main(int argc, char const *argv[]) { int msqid; //Creating a message queue msqid = msgget(IPC_PRIVATE,IPC_CREAT|0700); if (msqid < 0) { perror("msgget"); exit(-1); } printf("msqid is :%d ", msqid); return 0; }
每执行一次,就新建了一个消息队列,
添加消息
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
msqid 上个函数得到的消息队列ID
msgp 结构体
struct msgbuf { long mtype; /* message type, must be > 0 */消息类型 char mtext[1]; /* message data */消息正文 };
msgsz 要发送的消息的长度
msgflg IPC_NOWAIT 如果消息没有立即发送而调用进程会立即返回
0 msgsnd调用阻塞直到满足条件为止
成功返回 0 失败返回 -1
试用一下:
首先声明一个结构体用来传参,因为刚才已经申请了4个消息队列,所以我们直接使用第四个。
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> //save massage struct struct msgbuf { long mtype; char mtext[50]; }; int main(int argc, char const *argv[]) { int msqid = 4; int ret = 0; struct msgbuf MsgBuf; /* //Creating a message queue msqid = msgget(IPC_PRIVATE,IPC_CREAT|0700); if (msqid < 0) { perror("msgget"); exit(-1); } printf("msqid is :%d ", msqid); */ //Set message type MsgBuf.mtype = 2; //Write message content strcpy(MsgBuf.mtext,"ai ya wo cao"); //Block sending message ret = msgsnd(msqid,&MsgBuf,sizeof(MsgBuf),0); if(ret < 0) { perror("msgsnd"); exit(-1); } return 0; }
能看得出来,执行本程序过后,第四个消息队列多了1条消息,占用了64个字节。
读取消息
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
msqid 消息队列ID
msgp 结构体
msgsz 消息长度
msgtyp 0 接收第一个消息
>0 接收该类型的第一个消息
<0 返回队列种类型小于msgtyp的绝对值的消息
非0时,用于非先进先出次序读取消息
msgflg MSG_NOERROR 若实际接收字节比msgsz多,则只读msgsz
IPC_NOWAIT 不阻塞,如果没有所需类型的消息则直接返回-1
0 阻塞,直到满足条件为止
msgrcv函数成功执行后会将读取的消息从消息队列中删除
成功返回 0 失败返回 -1
试玩一下:
再声明一个同样的结构体去接收这个数据
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> //save massage struct struct msgbuf { long mtype; char mtext[50]; }; int main(int argc, char const *argv[]) { int msqid = 4; int ret = 0; struct msgbuf MsgBuf; struct msgbuf RcvBuf; /* //Creating a message queue msqid = msgget(IPC_PRIVATE,IPC_CREAT|0700); if (msqid < 0) { perror("msgget"); exit(-1); } printf("msqid is :%d ", msqid); */ /* //Set message type MsgBuf.mtype = 2; //Write message content strcpy(MsgBuf.mtext,"ai ya wo cao"); //Block sending message ret = msgsnd(msqid,&MsgBuf,sizeof(MsgBuf),0); if(ret < 0) { perror("msgsnd"); exit(-1); } */ //read massage from type 2 ret = msgrcv(msqid,&RcvBuf,64,2,0); if(ret < 0) { perror("msgrcv"); exit(-1); } printf("%s ",RcvBuf.mtext); return 0; }
可以看到,读出了数据,消息数-1
控制消息队列
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgctl(int msqid, int cmd, struct msqid_ds *buf);
msqid 消息队列ID
cmd 指令:
IPC_STAT 取出系统保存的消息队列的msqid_ds数据,并将其存入buf指向的msqid_ds结构中
IPC_SET 设定消息队列的 msqid_ds 中的 msg_perm成员
IPC_RMID 删除消息队列
buf 消息队列缓冲区
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #define BUFSZ 512 struct message{ long msg_type; char msg_text[BUFSZ]; }; int main() { int qid; key_t key; int len; struct message msg; /*根据不同的路径和关键表示产生标准的 key*/ if((key=ftok(".",'a'))== -1){ perror("ftok"); exit(1); } /*创建消息队列*/ if((qid=msgget(key,IPC_CREAT|0666))== -1){ perror("msgget"); exit(1); } printf("opened queue %d ",qid); puts("Please enter the message to queue:"); if((fgets((&msg)->msg_text,BUFSZ,stdin))==NULL){ puts("no message"); exit(1); } msg.msg_type = getpid(); len = strlen(msg.msg_text); /*添加消息到消息队列*/ if((msgsnd(qid,&msg,len,0))<0){ perror("message posted"); exit(1); } /*读取消息队列*/ if(msgrcv(qid,&msg,BUFSZ,0,0)<0){ perror("msgrcv"); exit(1); } printf("message is:%s ",(&msg)->msg_text); /*从系统内核中移走消息队列。*/ if((msgctl(qid,IPC_RMID,NULL))<0){ perror("msgctl"); exit(1); } exit(0); }
运行结果:
信号量
信号量相当于一个资源的计数器
比如给某一个资源加了一个信号量为5,那么就是说同时只允许5个进程使用/访问该资源。
每有一个进程使用他,信号量就 -1 .当信号量 == 0时,无法再让更多的进程使用,同时该进程进入等待。(P操作)
当进程使用完毕后,会释放使用的资源,则信号量 +1。然后唤醒刚刚等待的进程。(V操作)
信号量有两种:有名信号量、基于内存的信号量(无名信号量)。
创建/打开有名信号量
#include <fcntl.h> /* For O_* constants */ #include <sys/stat.h> /* For mode constants */ #include <semaphore.h> sem_t *sem_open(const char *name, int oflag); sem_t *sem_open(const char *name, int oflag,mode_t mode, unsigned int value);
name 信号量名字,不同的进程可以通过该名字打开同一个信号量
oflag O_CREAT 当没有name的信号量时创建新的
mode 新建信号量的权限
value 信号量的初始值
成功返回指向信号量的指针,失败返回SEM_FAILED
关闭有名信号量
#include <semaphore.h>
int sem_close(sem_t *sem);
sem 传入刚才得到的信号量指针。
成功返回 0 失败返回 -1
初始化基于内存的信号量
#include <semaphore.h> int sem_init(sem_t *sem, int pshared, unsigned int value);
sem 需要初始化的信号量指针
pshared 0 表示只能在线程内部使用,否则再进程中使用:需要放在共享内存处
value 初始化的值
成功返回 0 失败返回 -1
P操作
#include <semaphore.h>
int sem_wait(sem_t *sem);
如果信号量的值大于 0,sem_wait()函数将信号量值减 1 并立即返回,代表着获取到资
源,如果信号量值等于 0 则调用进程(线程)将进入睡眠状态,直到该值变为大于 0 时才将
信号量减 1 才返回。
函数成功返回 0,否则返回-1;参数 sem 为需要操作的信号量。
V操作
#include <semaphore.h>
int sem_post(sem_t *sem);
当一个进程(线程)使用完某个信号灯时,它应该调用 sem_post 来告诉系统申请的资
源已经用完。sem_post()函数与 sem_wait 函数的功能正好相反,它把所指定的信号灯的值加
1,然后唤醒正在等待该信号灯值变为正数的任意进程(线程)。
函数成功返回 0,否则返回-1;参数 sem 为需要操作的信号量。
销毁基于内存的信号量
#include <semaphore.h>
int sem_destroy(sem_t *sem);
删除有名信号量
#include <semaphore.h> int sem_unlink(const char *name);