进程间通信(IPC)详解 |
IPC 单机形式有管道(无名管道 和 FIFO)、消息队列、共享内存、信号、信号量。多机形式有socket、streams。
查看系统中的消息队列、共享内存、信号量的命令:
ipcs
删除指令
ipcrm -m "id" //删除共享内存
ipcrm -s "id" //删除信号量
ipcrm -q "id" //删除消息队列
一、管道
管道分为无名管道和有名管道。而管道通常指无名管道。
Linux一切皆文件
无名和有名管道都属于文件,管道通信实则都是在对文件进行读写操作。
两者都具管道的特性:先进先出(追加写文件,从头读文件并清除已读内容)
区别:
无名管道:
只能在具有亲缘关系的进程间使用;
读端和写端可以同时存在,所以可以双工通信;
根据管道特性,虽然可以读写同时存在,但是通常读写都是相对的,
为了避免自写自读的情况发生,我们只保留一组读写端进行单工通信
有名管道:
可以在无亲缘关系的管道间使用;
当打开一个FIFO时,产生下列影响:(通常默认阻塞)
(1. 在一般情况中(没有说明非阻塞 O_NONBLOCK),读打开要阻塞到某个其他进程为写打开此FIFO。
为写而打开一个FIFO要阻塞到某个其他进程为读而打开它。
(2. 若指定了O_NONBLOCK,为读打开,如果没有进程已经为写而打开此FIFO,则读打开立即返回。
为写打开,如果没有进程已经为读而打开一个FIFO,那么将出错返回,其errno是ENXIO。
O_RDONLY、O_WRONLY、O_RDWR均可用,所以可以同时往管道进行读写进行双工通信;
根据管道特性,虽然可以读写同时存在,但是通常读写都是相对的,为了避免自写自读的情况发生,
采用只读只写方式打开进行单工通信
无名管道简介
- 工作方式一般使用半双工,保留一组读写端
- 使用只限于具有亲缘关系的进程之间使用
- 管道可以理解为一种特殊的文件,可以使用read、write等函数进行读写(lseek不可用), 但它不是普通文件,存在于内存中
- 数据被读取后就消失,不可进行二次读取
- 读写方一旦确定便无法更改,因为调用close关闭文件描述符后就无法使用open打开会出错。
- 读管道
1.管道中有数据:read 返回实际读到的字节数
2.管道中无数据:
(1)管道写端被全部关闭:read返回0(好像读到文件结尾)
(2)写端没有被全部关闭:read阻塞等待数据到来 - 写管道
1.管道读端被全部关闭:进程异常终止(也可以使用捕捉SIGPIPE信号,使进程不终止)
2.管道读端没有被全部关闭
(1)管道已满,write阻塞
(2)管道未满,write将数据写入,并返回实际写入的字节数
pipe()函数 |
---|
#include <unistd.h>
int pipe(int pipefd[2]);
pipefd[2] 用于存放读和写的文件描述符 大小为2的数组
返回值:成功返回 0 失败返回-1 ,errno被设置
demo1 思路
- pipe创建管道
- fork创建进程
- 父进程往管道里写,子进程读管道里的数据并打印
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int pid;
int fd[2];
char r_buf[128] = {0};
if ((pipe(fd)) == -1) { //创建管道
perror("pipe fail");
exit(-1);
}
if ((pid = fork()) == -1) { //创建进程
perror("fork fail");
exit(-1);
}
if (pid > 0) { //父进程
close(fd[0]); //关闭读端
write(fd[1], "hallo world!", strlen("hallo world!")); //往管道里写
close(fd[1]); //关闭管道
} else if (pid == 0) {
close(fd[1]); //关闭写端
read(fd[0], r_buf, 128); //读取管道中的数据
printf("%s\n", r_buf); //打印数据
close(fd[0]); //关闭管道
}
return 0;
}
运行结果
二、FIFO
FIFO又称命名管道
简介
- 可以使用无亲缘的进程之间
- FIFO是一种文件类型,一经mkfifo创建一个FIFO,就可以使用open打开它,可以使用文件I/O函数。
- 当打开一个FIFO时,产生下列影响:(通常默认阻塞)
(1. 在一般情况中(没有说明非阻塞 O_NONBLOCK),读打开要阻塞到某个其他进程为写打开此FIFO。
为写而打开一个FIFO要阻塞到某个其他进程为读而打开它。
(2. 若指定了O_NONBLOCK,为读打开,如果没有进程已经为写而打开此FIFO,则读打开立即返回。
为写打开,如果没有进程已经为读而打开一个FIFO,那么将出错返回,其errno是ENXIO。
mkfifo()函数 |
---|
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
pathname 创建一个名字为"pathname"的FIFO特殊文件
mode 文件权限
返回值:成功返回 0 失败返回 -1 ,errno被设置
demo2思路
- mkfifo创建FIFO
- fork创建进程
- 父进程只写打开FIFO,并往FIFO里写如数据
- 子进程只读打开FIFO,并读取FIFO里的数据并打印
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
int main()
{
int fd;
int pid;
int sum = 3;
char r_buf[128] = {0};
if (((mkfifo("file", 0600)) == -1) && (errno != EEXIST)) { //创建FIFO,如果FIFO存在条件为假
perror("mkfifo fail");
exit(-1);
}
if ((pid = fork()) == -1) { //创建进程
perror("fork fail");
exit(-1);
} else if (pid > 0) { //父进程
fd = open("file", O_WRONLY); //打开FIFO
while(sum) //间隔一秒往FIFO里写入数据
{
write(fd, "message form fifo", strlen("message from fifo"));
sleep(1);
sum--;
}
close(fd); //关闭FIFO
} else if (pid == 0) { //子进程
fd = open("file", O_RDONLY); //打开FIFO
while (sum) //间隔一秒读取一次FIFO里的数据并打印
{
read(fd, r_buf, 128);
puts(r_buf);
sleep(1);
sum--;
}
close(fd); //关闭FIFO
}
return 0;
}
三、消息队列
简介
-
消息队列是消息的链表,存放在内核中并由消息队列标识符标识索引。我们将称消息队列为“队列”,其标识符为“队列ID”
-
可以按照数据的类型进行指定读,消息队列属于顺序队列形式的结构,向队列里写的每一条消息,会追加到队列后面,读取一个就从队列里消除一个
-
写函数不会阻塞,除非队列里存放的消息数量已经满了,才会导致写阻塞
-
可离线通讯,读写端无需同时存在
ftok()函数 |
---|
功能:获取key |
#include <sys/types.h> #include <sys/ipc.h> |
key_t ftok(const char *pathname, int proj_id); |
pathname 是指定的文件名(已经存在的文件名),一般使用当前目录 |
key = ftok(“.”, 1); 这样就是将pathname设为当前目录 |
proj_id 是子序号。只使用8bits(1-255)。将文件的索引节点号取出,前面加上子序号得到key_t的返回值。 |
成功返回key值, 失败返回-1,errno被设置 |
头文件
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
创建或打开队列,成功返回队列ID,失败返回-1,errno被设置
int msgsnd(int msqid, const void *msgp, size_t msgsz, int flag);
写入数据,成功返回0,失败返回-1, errno被设置
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int flag);
读取数据,成功返回数据的字节数,失败返回-1, errno被设置
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
根据cmd(删除队列、设置数据元素、读取数据结构)命令执行操作。失败返回-1,errno被设置
key | 由ftok生成,key为队列ID的组成部分 |
---|---|
msgflg | 标志位(IPC_CREAT、IPC_EXCL、 IPC_NOWAIT)还应加上队列权限(读、写、执行) |
msqid | 队列ID |
msgp | 消息缓冲区指针(结构体指针) |
flag | 为0表示阻塞方式(队列空间不够就会堵塞),设置IPC_NOWAIT 表示非阻塞方式 |
msgsz | 消息数据长度,不含数据类型长度4个字节 |
msgtyp | 数据类型 type == 0 返回队列中的第一个消息。type > 0 返回队列中消息类型为type的第一个消息。type < 0返回队列中消息类型值小于或等于type绝对值,而且在这种消息中,其类型值又最小的消息 |
cmd | 控制命令(IPC_STAT(读取数据结构)、IPC_SET(设置数据元素)、IPC_RMID(删除队列)) |
buf | 读取和设置操作的缓冲区指针,不使用设置为NULL |
demo3思路
- fork创建进程
- ftok创建key
- 父进程创建消息队列,并往消息队列里写入数据
- 子进程打开消息队列,读取数据并打印,最后删除该消息队列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
struct msgbuf {
long mtype;
char mtext[128];
};
int main()
{
int key;
int pid;
int msgid;
struct msgbuf wbuf;
struct msgbuf rbuf;
pid = fork(); //创建进程
if (pid == -1) {
perror("fork fail");
exit(1);
}
key = ftok(".", 1); //创建key
if (key == -1) {
perror("ftok fail");
exit(2);
}
if (pid > 0) { //父进程
msgid = msgget(key, IPC_CREAT | 0777); //创建消息队列
if (msgid == -1) {
perror("msgget fail");
exit(3);
}
wbuf.mtype = 1; //创建数据
memset(wbuf.mtext, '0', sizeof(wbuf.mtext));
strcpy(wbuf.mtext, "message from message queuing");
msgsnd(msgid, &wbuf, sizeof(wbuf) - sizeof(long), 0); //发送数据,空间不够就会堵塞
} else if (pid == 0) { //子进程
msgid = msgget(key, IPC_CREAT | 0777); //打开消息队列
if (msgid == -1) {
perror("msgget fail");
exit(4);
}
msgrcv(msgid, &rbuf, sizeof(rbuf) - sizeof(long), 1, 0); //读取数据,无数据就会堵塞
printf("type = %ld msg: %s\n", rbuf.mtype, rbuf.mtext);
msgctl(msgid, IPC_RMID, NULL); //删除消息队列
}
return 0;
}
四、共享内存
简介
共享存储允许两个或多个进程共享一给定的存储区。因为数据不需要在客户机和服务器之间复制,所以这是最快的一种 IPC。使用共享存储的唯一窍门是多个进程之间对一给定存储区的同步存取。若服务器将数据放入共享存储区,则在服务器做完这一操作之前,客户机不应当去取这些数据。通常,信号量被用来实现对共享存储存取的同步。
#include <sys/ipc.h>
#include <sys/shm.h>
创建或获取共享内存并获取共享内存标识符,成功返回共享内存ID,失败返回-1,errno被设置
int shmget(key_t key, size_t size, int shmflg);
将共享内存挂载到进程的内存空间,成功返回指向共享内存的指针,失败返回-1,errno被设置
void *shmat(int shmid, const void *shmaddr, int shmflg);
卸载共享内存,成功返回0,失败返回-1,errno被设置
int shmdt(const void *shmaddr);
删除共享内存,成功返回0,失败返回-1,errno被设置
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- size
大于0的整数:新建的共享内存大小,以字节为单位
0:只获取共享内存时指定为0 - shmflg (shmget)
0:取共享内存标识符,若不存在则函数会报错
IPC_CREAT
IPC_CREAT | IPC_EXCL
还应加上内存权限(读、写) - shmaddr (shmat)
指定共享内存出现在进程内存地址的什么位置,直接指定为NULL让内核自己决定一个合适的地址位置 - shmflg (shmat)
0:内存权限可读可写
SHM_RDONLY:为只读模式 - cmd
IPC_STAT:得到共享内存的状态,把共享内存的shmid_ds结构复制到buf中
IPC_SET:改变共享内存的状态,把buf所指的shmid_ds结构中的uid、gid、mode复制到共享内存的shmid_ds结构内
IPC_RMID:删除这片共享内存 - buf
是一个结构体指针。IPC_STAT的时候,取得的状态放在这个结构体中。如果要改变共享内存的状态,用这个结构体指定
demo4思路
- fork创建进程
- ftok获取key
- 父进程shmget创建共享内存,shmat挂载共享内存到进程当中,strcpy写入数据,shmdt卸载共享内存
- 子进程shmget创建共享内存,shmat挂载共享内存到进程当中,打印数据,shmdt卸载共享内存,shmctl删除共享内存
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int pid;
int key;
int shmid;
char *shmaddr;
pid = fork(); //创建进程
if (pid == -1) {
perror("fork fail");
exit(-1);
}
key = ftok(".", 2); //获取key
if (key == -1) {
perror("ftok fail");
exit(-1);
}
if (pid > 0) { //父进程
shmid = shmget(key, 1024*4, IPC_CREAT | 0666); //创建共享内存
if (shmid == -1) {
perror("shmget fail");
exit(-1);
}
shmaddr = shmat(shmid, NULL, 0); //挂载内存到进程空间中
strcpy(shmaddr, "message from share memory"); //写入数据
shmdt(shmaddr); //卸载共享内存
} else if (pid == 0) { //子进程
shmid = shmget(key, 0, IPC_CREAT); //获取共享内存ID
if (shmid == -1) {
perror("shmget fail");
exit(-1);
}
shmaddr = shmat(shmid, NULL, 0); //挂载内存到进程空间中
printf("%s\n", shmaddr); //打印共享内存中的数据
shmdt(shmaddr); //卸载共享内存
shmctl(shmid, IPC_RMID, NULL); //删除共享内存
}
return 0;
}
五、信号
信号的最大意义不是为了杀死进程,而是实现一些异步通讯的手段。即捕获到指定信号去执行用户自定义函数
特性:简单,但是不能携带大量信息,需要满足某个特定条件才能发送
1.指令
查看Linux中的信号的指令: kill -l
向进程发送信号的指令:kill 参数 进程号
也可以使用kill(int pid, int signum)函数发送信号
2.信号的处理方式
进程对接收到的信号有三种处理方式:
① 默认动作处理
默认执行信号的原先设定的动作
② 忽略 宏(SIG_IGN)
忽略信号,(SIGKILL 和 SIGSTOP)不能被忽略
③ 捕获并处理
捕获到指定信号,通知内核,然后去调用执行我们自己写的用户函数,但是注意(SIGKILL 和SIGSTOP)不能捕获
3. 信号注册函数
每一个信号都对应着一个信号处理函数,当进程接收到某个信号时,就会立即去执行对应的信号处理函数,信号处理函数的注册有两种:
入门版:signal
进阶版:sigaction (可携带信息)
#include <signal.h>
typedef void (*sighandler_t)(int); //定义函数指针类型
sighandler_t signal(int signum, sighandler_t handler);
signum 捕获的目标信号 handler 自定义信号处理函数(参数为信号量(int signum))
成功返回的是一个指向某个函数的指针,而这个函数就是上次处理这个信号的信号处理函数,如果未处理过,那就是NULL
失败返回SIG_ERR
demo5 思路
- 编写demo5.c调用信号注册函数signal,当捕获SIGINT或SIGKILL立即执行自定义处理函数handler,如果捕获到SIGUSR1,则忽略该信号
- 编写signalHandler.c通过调用程序去发送信号
demo5.c
#include <stdio.h>
#include <signal.h>
void handler(int signum)
{
printf("get signum = %d\n", signum);
switch(signum)
{
case 2:
printf("SIGINT\n");
break;
case 9:
printf("SIGKILL\n");
break;
case 10:
printf("SIGUSR1\n");
}
printf("never quit!\n");
}
int main()
{
signal(SIGINT, handler);
signal(SIGKILL, handler);
signal(SIGUSR1, SIG_IGN);
while(1);
return 0;
}
signalHandler.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
int main(int argc, char **argv)
{
int signum;
int pid;
char cmd[20];
if (argc != 3) {
printf("sum false!\n");
exit(-1);
}
//方式一
pid = atoi(argv[2]);
signum = atoi(argv[1]);
kill(pid, signum);
/*方式二
sprintf(cmd, "kill -%s %s", argv[1], argv[2]);
system(cmd);
*/
return 0;
}
高级版信号处理注册函数 sigaction
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
struct sigaction {
void (*sa_handler)(int); //信号处理不接受数据,SIG_IGN 为忽略,SIG_DFL 为默认动作
void (*sa_sigaction)(int, siginfo_t *, void *); //信号处理接受数据和sigqueue配合使用,void *为空无数据,不为空有数据
sigset_t sa_mask;//阻塞关键字的信号集,可以再调用捕捉函数之前,把信号添加到信号阻塞字,信号捕捉函数返回之前恢复为原先设置。
int sa_flags;//设置为 SA_SIGINFO 表示接受数据
};
//回调函数句柄sa_handler、sa_sigaction只能任选其一
siginfo_t {
int si_signo; /* 信号的编号 */
int si_errno; /* 一个errno值 */
int si_code; /* 信号码 */
int si_trapno; /* 导致硬件生成信号(在大多数体系结构上未使用)*/
pid_t si_pid; /* 发送进程ID */
uid_t si_uid; /* 发送进程的实际用户ID */
int si_status; /* 退出值或信号 */
clock_t si_utime; /* 消耗的用户时间 */
clock_t si_stime; /* 消耗的系统时间 */
sigval_t si_value; /* 存放信号携带的信息,si_value为共用体与发送信号时的value共用体一致 */
int si_int; /* POSIX.1b信号 */
void *si_ptr; /* POSIX.1b信号 */
int si_overrun; /* 计时器溢出计数;POSIX.1b计时器*/
int si_timerid; /* 计时器ID;POSIX.1b计时器 */
void *si_addr; /* 导致故障的内存位置*/
long si_band; /* band事件(was int in)glibc 2.3.2及更早版本)*/
int si_fd; /* 文件描述符 */
short si_addr_lsb; /* 地址的最低有效位(从内核2.6.32开始*/
}
signum:为信号的编号
act:不为空则说明自定义信号处理函数
oldact:不为空说明备份上一次使用的信号处理函数以便再次调用
sa_mask:作用为了保证正在处理一个信号时又捕获这个信号,那么会阻塞到当前信号处理完毕为止。信号处理函数返回后恢复原来所有设置
高级版信号发送函数 sigqueue
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval {
int sival_int;
void *sival_ptr;
};
使用前提
信号处理函数设置为能够接受数据(即使用 sa_sigaction 和配置 sa_flags = SA_SIGINFO)
注意事项
信号携带的信息为字符串的话只能在同一程序中实现。或者使用共享内存配合信号实现
demo6 思路
- 编写 receive.c 用于接收并处理信号
- 编写 send.c 用于发送信号,信号携带一个整型数和一个字符串,由于收发端为不同程序信号无法直接携带字符串,将使用共享内存实现
receive.c
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
void handler(int signum, siginfo_t *info, void *content)
{
int shmid;
int key;
char *shmaddr;
//创建共享内存
key = ftok(".", 10);
shmid = shmget(key, 0, IPC_CREAT | 0666);
shmaddr = shmat(shmid, NULL, 0);
printf("get signum = %d\n", signum);
//打印信号携带的信息
if (content != NULL) { //判断信号是否有携带信息
printf("from pid = %d\n", info->si_pid);
printf("get message character string = %s int data = %d\n",shmaddr, info->si_value.sival_int);
}
shmdt(shmaddr);
shmctl(shmid, IPC_RMID, NULL);
}
int main()
{
struct sigaction act;
struct sigaction oldact;
act.sa_sigaction = handler;
act.sa_flags = SA_SIGINFO;
printf("my pid = %d\n", getpid());
sigaction(10, &act, &oldact);
while(1);
return 0;
}
send.c
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
int main(int argc, char **argv)
{
int pid;
int key;
int shmid;
int signum;
char *shmaddr;
union sigval value;
if (argc != 3) {
printf("sum false!\n");
exit(-1);
}
//创建共享内存
key = ftok(".", 10);
shmid = shmget(key, 1024, IPC_CREAT | 0666);
shmaddr = shmat(shmid, NULL, 0);
strcpy(shmaddr, "hallo");
shmdt(shmaddr);
//发送信号
pid = atoi(argv[2]);
signum = atoi(argv[1]);
printf("my pid = %d\n", getpid());
value.sival_int = 100;
sigqueue(pid, signum, value);
return 0;
}
六 信号量
概念
- 信号量是一个计数器,常用于处理进程或线程的同步问题,特别是对临界资源的同步访问。
- 一次仅允许一个进程使用的资源称为临界资源(锁),这里的资源可以是一段代码、一个变量或某种硬件资源
- 如果需要在进程间传递数据需要配合共享内存实现
- 信号量就是起到钥匙的功能, P操作(拿锁):信号量减1,V操作(放回锁):信号量加1,PV操作可以加减任意正整数
Linux下信号量函数都是在通用信号量数组上进行操作的,不是一个单一的二值信号量进行操作
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
创建或获取一个信号量组 成功返回信号量集ID 失败返回-1 errno被设置
int semget(key_t key, int nsems, int semflg);
控制信号量的相关信息 成功返回非负值 失败返回-1
int semctl(int semid, int semnum, int cmd, ...);
对信号量组进行操作,改变信号量的值 成功返回0 失败返回-1
int semop(int semid, struct sembuf *sops, size_t nsops);
sops:指向信号量操作结构体指针 nsops: 信号量操作结构体的数量(大于等于1)
struct sembuf {
unsigned short sem_num; // semaphore number
short sem_op; // semaphore operation (信号量的加减操作)
short sem_flg; // 操作方式设置为SEM_UNDO(当进程终止的时候自动取消对锁的操作) 使用SEM_NOWAIT(无意义)
};
需要用户自行定义 通常使用第一个元素
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)
};
nsems 是该集合中的信号量数。如果是创建新集合(一般在服务器中),则必须指定nsems,通常为1。如果引用一个现存的集合(一个客户机),则将nsems指定为0。
semflg IPC_CREAT 或者 IPC_CREAT | IPC_EXCL 并设置读写权限
cmd
d参数指定下列十种命令中的一种,使其在 semid指定的信号量集合上执行此命令。其中
有五条命令是针对一个特定的信号量值的,它们用 semnum指定该集合中的一个成员。semnum
值在0和nsems-1之间(包括0和nsems-1)。
• IPC_STAT 对此集合取semid_ds结构,并存放在由arg.buf指向的结构中。
• IPC_SET 按由arg.buf指向的结构中的值设置与此集合相关结构中的下列三个字段值:
sem_perm.uid , sem_perm.gid和sem_perm.mode。此命令只能由下列两种进程执行:一种是其有
效用户ID等于sem_perm.cuid或sem_perm.uid的进程;另一种是具有超级用户特权的进程。
• IPC_RMID 从系统中删除该信号量集合。这种删除是立即的。仍在使用此信号量的
其他进程在它们下次意图对此信号量进行操作时,将出错返回 EIDRM。此命令只能由下列两
种进程执行:一种是具有效用户ID等于sem_perm.cuid或sem_perm.uid的进程;另一种是具有超
级用户特权的进程。
• GETVAL 返回成员semnum的semval值。
• SETVAL 设置成员semnum的semval值。该值由arg.val指定。
• GETPID 返回成员semnum的sempid值。
• GETNCNT 返回成员semnum的semncnt值。
• GETZCNT 返回成员semnum的semzcnt值。
• GETALL 取该集合中所有信号量的值,并将它们存放在由arg.array指向的数组中。
• SETALL 按arg.array指向的数组中的值设置该集合中所有信号量的值。
··· 用户自定义 共用体 union semun
demo7 思路
使用信号量配合共享内存、消息队列进行进程间通信
发送端根据用户输入进行发送操作或则退出操作,发送操作:先执行P操作,用户输入需要发送的信息,发送数据,消息队列用于提示接收端读取数据,最后执行V操作,退出操作:使用消息队列向接收端发送信息提示退出,卸载共享内存后退出。 接收端: 去读消息队列里的提示信息(无数据就会堵塞),提示信息为读取数据:执行P操作,然后读取共享内存里的数据并打印,最后执行V操作,若提示消息为退出,卸载共享内存,删除消息队列、共性内存、信号量后退出
客户端
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <stdio.h>
#include <string.h>
struct msgbuf
{
long mtype;
char mtext[128];
};
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
void seminit(int semid, int num)
{
union semun sem;
sem.val = 1;
semctl(semid, num, SETVAL, sem);
}
void sem_p(int semid, int num)
{
struct sembuf sbuf;
sbuf.sem_num = num;
sbuf.sem_op = -1;
sbuf.sem_flg = SEM_UNDO;
semop(semid, &sbuf, 1);
}
void sem_v(int semid, int num)
{
struct sembuf sbuf;
sbuf.sem_num = num;
sbuf.sem_op = 1;
sbuf.sem_flg = SEM_UNDO;
semop(semid, &sbuf, 1);
}
void welcome()
{
printf("----------------------------------------\n");
printf("--------------welcome to ipc------------\n\n");
printf("------------input r send datas----------\n");
printf("---------------input q quit------------\n\n");
printf("----------------------------------------\n");
}
int main()
{
int key;
int shmid,msgid,semid;
char *shmaddr;
struct msgbuf r_buf;
key = ftok(".", 10);
msgid = msgget(key, IPC_CREAT | 0777);
shmid = shmget(key, 0, IPC_CREAT | 0666);
shmaddr = shmat(shmid, NULL, 0);
semid = semget(key, 1, IPC_CREAT | 0666);
welcome();
while(1)
{
printf("please input your operation\n");
memset(r_buf.mtext, '0', sizeof(r_buf.mtext));
scanf("%s", r_buf.mtext);
if (strcmp(r_buf.mtext, "r") == 0) {
sem_p(semid, 0);
printf("plase input you want send datas\n");
scanf("%s", shmaddr);
r_buf.mtype = 666;
msgsnd(msgid, &r_buf, sizeof(r_buf) - sizeof(long), 0);
sem_v(semid, 0);
} else if (strcmp(r_buf.mtext, "q") == 0) {
r_buf.mtype = 666;
msgsnd(msgid, &r_buf, sizeof(r_buf) - sizeof(long), 0);
shmdt(shmaddr);
break;
}
}
return 0;
}
服务端
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct msgbuf
{
long mtype;
char mtext[128];
};
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
void seminit(int semid, int num)
{
union semun sem;
sem.val = 1;
semctl(semid, num, SETVAL, sem);
}
void sem_p(int semid, int num)
{
struct sembuf sbuf;
sbuf.sem_num = num;
sbuf.sem_op = -1;
sbuf.sem_flg = SEM_UNDO;
semop(semid, &sbuf, 1);
}
void sem_v(int semid, int num)
{
struct sembuf sbuf;
sbuf.sem_num = num;
sbuf.sem_op = 1;
sbuf.sem_flg = SEM_UNDO;
semop(semid, &sbuf, 1);
}
int main()
{
int key;
int shmid,msgid,semid;
char *shmaddr;
char *buf;
struct msgbuf r_buf;
key = ftok(".", 10);
msgid = msgget(key, IPC_CREAT | 0777);
shmid = shmget(key, 1024, IPC_CREAT | 0666);
shmaddr = shmat(shmid, NULL, 0);
semid = semget(key, 1, IPC_CREAT | 0666);
seminit(semid, 0);
while(1)
{
msgrcv(msgid, &r_buf, sizeof(r_buf) - sizeof(long), 666, 0);
if (strcmp(r_buf.mtext, "r") == 0) {
sem_p(semid, 0);
printf("%s\n", shmaddr);
sem_v(semid, 0);
} else if (strcmp(r_buf.mtext, "q") == 0) {
shmdt(shmaddr);
union semun un;
shmctl(shmid, IPC_RMID, NULL);
msgctl(msgid, IPC_RMID, NULL);
semctl(semid, 0, IPC_RMID, un);
exit(-1);
break;
}
}
return 0;
}
运行结果
服务端
hallo
客户端
----------------------------------------
--------------welcome to ipc------------
------------input r send datas----------
---------------input q quit------------
----------------------------------------
please input your operation
r
plase input you want send datas
hallo
please input your operation
q
总结
Linux一切皆文件,管道速度慢,无名管道容量有限
无名和有名管道都属于文件,管道通信实则都是在对文件进行读写操作。
两者都具管道的特性:先进先出(追加写文件,从头读文件并清除已读内容)
区别:
无名管道:
只能在具有亲缘关系的进程间使用;
读端和写端可以同时存在,所以可以双工通信;
根据管道特性,虽然可以读写同时存在,但是通常读写都是相对的,
为了避免自写自读的情况发生,我们只保留一组读写端进行单工通信
有名管道:
可以在无亲缘关系的管道间使用;
当打开一个FIFO时,产生下列影响:(通常默认阻塞)
(1. 在一般情况中(没有说明非阻塞 O_NONBLOCK),读打开要阻塞到某个其他进程为写打开此FIFO。
为写而打开一个FIFO要阻塞到某个其他进程为读而打开它。
(2. 若指定了O_NONBLOCK,为读打开,如果没有进程已经为写而打开此FIFO,则读打开立即返回。
为写打开,如果没有进程已经为读而打开一个FIFO,那么将出错返回,其errno是ENXIO。
O_RDONLY、O_WRONLY、O_RDWR均可用,所以可以同时往管道进行读写进行双工通信;
根据管道特性,虽然可以读写同时存在,但是通常读写都是相对的,为了避免自写自读的情况发生,
采用只读只写方式打开进行单工通信
-
消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题
-
共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存
-
信号: 传递字符串消息只能在同一程序下的进程间
-
信号量:不能传递复杂消息,只能用来同步 ,通常配合消息队列、共享内存使用