进程通信
目的
- 数据传输
- 资源共享
- 控制进程
- 通知事件
通信分类
管道
- 匿名管道
- 命名管道
System V IPC
- System V 消息队列
- System V 共享内存
- System V 信号量
POSIX IPC
- 消息队列
- 共享内存
- 信号量
- 互斥量
- 条件变量
- 读写锁
管道通信
管道是一个进程到另一个进程之间的数据流
匿名管道
匿名管道适合父子进程之间通信的场景。
用pipe函数来创建一个匿名管道连接父子进程,从而实现进程之间的通信:
int pipe(int fd[]);
fd:文件描述符数组,其中fd[0]表示读端,fd[1]表示写端。
返回值:成功创建管道返回0,失败返回错误代码。
#include <stdio.h>
#include <unistd.h>
int main()
{
int fds[2] = {0};
int ret = pipe(fd);
if (0 != ret){
perror("pipe\n");
exit(1);
}
pid_t pid = fork()
if (0 == pid){
close(fds[0]);
const char* str = "I am child";
write(fd[1], str, strlen(str));
close(fd[1]);
exit(0);
}
else if (pid > 0){
close(fd[1]);
char buf[1024] = {0};
read(fd[0], buf, sizeof(buf));
waitpid(pid, NULL, 0);
}
return 0;
}
规则:
当没有数据可读时
O_NONBLOCK disable:read调用阻塞,管道内有数据来时唤醒。
O_NONBLOCK enable:read 调用返回-1,errno值为EAGAIN。
当管道满时
O_NONBLOCK disable:write调用阻塞,管道内数据被读走时唤醒。
O_NONBLOCK enable:write调用返回-1,errno值为EAGAIN。
所有管道写端对应的文件描述符被关闭时,read返回0
所有管道读端对应的文件描述符被关闭时,write操作会产生信号SIGPIPE,进而导致write进程退出
写入数据量不大于PIPE_BUF时,linux将保证写入的原子性,否则不再保证
特点
只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信,通常来说,一个管道由另一个进程创建,然后该进程调用fork(),此后父子进程之间就可应用该管道通信
管道提供流式服务
一般而言,管道的生命周期随进程
一般而言,内核会对管道操作进行同步与互斥
管道是半双工的,数据只能向一个方向流动,需要双方通信时,需要建立两个管道
命名管道
命名管道可以解决非父子进程场景下的通信问题,不同于匿名管道,使用命名管道会在用户目录下会生成一个FIFO特殊类型文件。该文件可以像普通文件一样打开,也能进行普通文件一样的操作。生成该文件既可用命令生成,也可在程序内用函数生成。这个命令和函数名称都为mkfifo。
int mkfifo(const char* filename, mode_t mode);
参数
filename显而易见为文件名
mode和open函数中的mode相同。
返回值
成功创建时返回0,失败时返回-1。
//proc_write.c
int main()
{
if ((mkfifo("fifo", 00644) < 0){
perror("mkfifo");
return 1;
}
fd = open("fifo", O_WRONLY, 00644);
char* buf = "Hello World!";
write(fd, buf, strlen(buf));
close(fd);
return 0;
}
//proc_read.c
int main()
{
int fd = open("fifo", O_RDONLY, 00644);
char buf[1024] = {0};
read(fd, buf, sizeof(buf));
printf("%s\n", buf);
close(fd);
return 0;
}
匿名管道与命名管道的区别
匿名管道由pipe函数创建并打开
命名管道由mkfifo函数创建,打开用open
FIFO与pipe之间唯一的区别就在于它们创建与打开的方式不同,一旦这些工作完成之后,她们具有相同的语义。
命名管道的打开规则
如果当前打开操作是为读而打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
O_NONBLOCK enable:立刻返回成功
如果当前打开操作是为写而打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
O_NONBLOCK enable:立刻返回失败,错误码为ENXIO
System V 共享内存
共享内存函数
key_t ftok(const char* pathname, int proj_id);
功能:产生一个键值
参数
pathname:文件名
proj_id:用来取低8位和文件inode的低16位来合成key_t键值,所以proj_id的取值范围为0~255。
返回值
成功时返回key_t,失败时返回-1
int shmget(key_t key, size_t size, int shmflg)
功能:创建一个共享内存
参数
key:这个共享内存的键值
size:共享内存的大小
shmflg:由九个权限标志构成,用法和创建文件时使用的mode模式标志相同。
返回值:
成功返回一个非负整数,即该共享内存的标识码,失败返回-1。
void* shmat(int shmid, const void* shmaddr, int shmflg);
功能:将共享内存段链接至进程地址空间
参数
shmid:共享标识符
shmaddr:指定链接的地址
shmflg:它的两个取值可能是SHM_RND和SHM_RDONLY
返回值:
成功返回一个指针,指向共享内存第一个字节,失败返回-1。
说明:
shmaddr为NULL时,核心自动选择一个地址
shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址
shmaddr不为NULL且shmflg设置为SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr-(shmaddr % SHMLBA)
shmflg = SHM_RDONLY,表示连接操作用来只读共享内存
int shmat(const void* shmaddr);
功能:将共享内存段与当前进程脱离
参数
shmaddr:由shmat返回的指针
返回值
成功返回0,失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值
成功返回0,失败返回-1
cmd | 说明 |
---|---|
IPC_STAT | 把shmid_ds结构中的数据设置为共享内存的当前关联值 |
IPC_SET | 在进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds数据结构中给出的值 |
IPC_RMID | 删除共享内存段 |
//server.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main()
{
int k = ftok("myfile", 0x6666);
int shmid = shmget(k, 4096, IPC_CREAT|IPC_EXCL|0644);
if (shmid < 0){
perror("shmget");
return 1;
}
char* mem = shmat(shmid, NULL, 0);
char c = 'a';
while (c <= 'z'){
mem[c-'a'] = c;
c++;
sleep(1);
}
shmdt(mem);
shmctl(shmid, IPC_RMID, NULL);
return 0;
//client.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
int main()
{
int k = ftok("myfile", 0x6666);
int shmid = shmget(k, 4096, IPC_CREAT);
if (shmid < 0){
perror("shmget");
return 1;
}
char* mem = shmat(shmid, NULL, 0);
while (1){
if (strlen(mem) > 0)
printf("%s\n", mem);
else
break;
}
shmdt(mem);
shmctl(shmid, IPC_RMID, NULL);
return 0;
注意:共享内存没有同步与互斥