Linux进程间通信
每一个进程之间具有独立性,因此无法直接通信所以才需要操作系统提供进程间通信方式,实现进程间的通信。
操作系统针对不同的通信场景提供了多种不同的通信方式:数据传输–管道/消息队列;数据共享–共享内存;进程控制–信号量。
1.管道
本质:内核中的一块缓冲区,多个进程访问同一个管道就可以实现通信。
种类:匿名管道/命名管道。
匿名管道
内核中的缓冲区没有具体的标识符,因此只能用于具有亲缘关系的进程间通信。
因为只能通过子进程复制父进程的方式获得到管道的操作句柄,因为父进程创建管道的时候操作系统会返回管道的操作句柄。
匿名管道的创建:
int pipe(int pipedfd[2]);//具有两个int型节点数组的首地址,用于接收管道返回操作句柄
pipefd[0]:用于从管道中读取数据。
pipefd[1]:用于从管道中写入数据。
具体代码实现:
int main()
{
int pipefd[2] = {-1};
int ret = pipe(pipefd);
pid_t pid = fork();
if(pid == 0)
{
char buf[1024] = {0};
read(pipefd[0], buf, 1023);
printf("%s", buf);
}
else
{
char* ptr = "hello world\n";
write(pipefd[1], ptr, strlen(ptr));
}
return 0;
}
管道是一个半双工通信,要不然只能读,要不然只能写(单向传输)。
管道的读写特性
1.若管道中没有数据,则调用read读取数据会阻塞。
2.若管道中数据满了,则调用write写入数据会阻塞;管道是一块缓冲区,并非无限制大。
3.若管道的所有读端pipefd[0]被关闭,则继续调用write会产生异常导致进程退出。
4.若管道的所有写端pipefd[1]被关闭,则继续调用read,read读完管道中的数据后不再阻塞,而是返回0。
命令行中管道符的实现:ps -ef | grep ssh
int main()
{
int pipefd[2] = {-1};
if(pipe(pipefd) < 0)
{
return -1;
}
pid_t ps_pid = fork();
if(ps_pid == 0)
{
dup2(pipefd[1], 1);//将标准输出重定向到管道写入端
execlp("ps", "ps", "-ef", NULL);
exit(0);
}
pid_t grep_pid = fork();
if(grep_pid == 0)
{
close(pipefd[1]);
dup2(pipefd[0], 0);//将标准输入重定向到管道读取端
execlp("grep", "grep", "ssh", NULL);
exit(0);
}
close(pipefd[0]);
close(pipefd[1]);
waitpid(ps_pid, NULL, 0);
waitpid(grep_pid, NULL, 0);
return 0;
}
命名管道
内核中的缓冲区,这块缓冲区具有标识符,可以用于同一主机上的任意进程间通信。
命名管道的创建:
int mkfifo(char* filename, mode_t mode);
filename:管道文件名称;mode:管道文件权限;成功返回0,失败返回-1。
具体代码实现:
读端:read.c
int main()
{
umask(0);
int ret = mkfifo("./test.fifo", 0664);
if(ret < 0 && errno != EEXIST)
{
return -1;
}
int fd = open("./test.fifo", O_RDONLY);
while(1)
{
char buf[1024] = {-1};
int ret = read(fd, buf, 1023);
if(ret == 0)
{
printf("all writr close");
return -1;
}
printf("read buf:%s\n", buf);
}
close(fd);
return 0;
}
写端:write.c
int main()
{
umask(0);
int ret = mkfifo("./test.fifo", 0664);
if(ret < 0 && errno != EEXIST)
{
return -1;
}
int fd = open("./test.fifo", O_WRONLY);
int i = 0;
while(1)
{
char buf[1024] = {-1};
sprintf(buf, "hello world+%d", i++);
write(fd, buf, strlen(buf));
sleep(1);
}
close(fd);
return 0;
}
命名管道的打开特性:
1.若文件以只读打开,则会阻塞,直到文件被以写的方式打开。
2.若文件以只写打开,则会阻塞,直到文件被以读的方式打开。
管道的特性
1.管道是半双通信。
2.管道读写特性。
3.管道生命周期随进程。
4.管道提供字节流传输服务。
5.命名管道额外有打开特性。
6.管道自带同步与互斥。
同步:通过调节判断实现临界资源操作的合理性。
互斥:通过唯一访问实现临界资源操作的安全性。
2.共享内存
在物理内存上开辟一块内存空间,多个进程可以将同一块物理内存空间映射到自己的虚拟地址空间,通过自己的虚拟地址直接访问这块空间,通过这种方式实现数据共享。共享内存是最快的进程间通信方式。
共享内存的操作流程
int shmget(key_t key, size_t size, int shmflg);//创建共享内存
key:内核中共享内存的标识符。
size:共享内存大小。
shmflg:IPC_CREAT–存在则打开,不存在则创建 | IPC_EXCL与IPC_CREAT同时使用,若存在则报错,不存在创建 | mode
返回值:成功返回一个非负整数,失败返回-1。
void* shmat(int shmid, const void* shmaddr, int shmflg);//映射共享内存
shmid:shmget返回的共享内存操作句柄。
shmaddr:共享内存映射在虚拟地址空间中的首地址–通常置NULL。
shmflg:映射成功之后对共享内存可以进行的操作。SHM_RDONLY用于只读/0-默认可读可写。
返回值:返回共享内存映射在虚拟地址空间中的首地址—通过这个首地址进行后续的内存操作。失败返回(void*)-1。
int shmdt(const void* shmaddr)//接触映射关系
shmaddr:映射在虚拟地址空间中的首地址。
返回值:成功返回0,失败返回-1。
int shmctl(int shmid, int cmd, struct shmid_ds* buf);//删除共享内存
shmid:共享内存操作句柄。
cmd:对共享内存想要进行的操作。IPC_RMID-删除共享内存。
buf:用于获取/设置共享内存信息的结构,不使用则置NULL。
返回值:成功返回0,失败返回-1。
具体代码实现
读端:read.c
#define IPC_KEY 0x12345678
int main()
{
int shm_id = shmget(IPC_KEY, 32, IPC_CREAT|0664);
void* shm_start = shmat(shm_id, NULL, 0);
while(1)
{
printf("[%s]\n", shm_start);
sleep(1);
}
shmdt(shm_start);
shmctl(shm_id, IPC_RMID, NULL);
return 0;
}
写端:write.c
#define IPC_KEY 0x12345678
int main()
{
int shm_id = shmget(IPC_KEY, 32, IPC_CREAT|0664);
void* shm_start = shmat(shm_id, NULL, 0);
int i = 0;
while(1)
{
sprintf(shm_start, "%s+%d", "hello", i++);
sleep(1);
}
shmdt(shm_start);
shmctl(shm_id, IPC_RMID, NULL);
return 0
}
共享内存删除的时候并不会立即被删除,只是将其状态置为销毁状态,移除标识(不让共享内存继续被其它进程映射链接),然后等到当前共享内存的链接数为0时,才会删除共享内存。
共享内存并没有自带同步与互斥,多个进程进行访问的时候存在安全问题。
共享内存的特性
1.最快的进程间通信方式。
2.生命周期随内核。
3.消息队列
内核中的一个优先级队列,多个进程通过访问同一个队列,进行添加节点或者获取节点实现通信。
创建步骤
int msgget(key_t key, int msgflg);//在内核中创建一个优先级队列
int msgsnd(int msqid, const void* msgp, size_t msgsz, int msgflg);//发送一个消息到消息队列
ssize_t msgcv(int msqid, void* msgp, size_t msgsz, long msgtyp, int msgflg);//从消息队列接收一个消息
int msgctl(int msqid, int cmd, struct msqid_ds* bug);//删除消息队列
消息队列特性
1.自带同步与互斥。
2.生命周期随内核。
4.信号量
适用于实现进程间的同步与互斥的(共享内存本身是不提供同步与互斥的,因此需要使用信号量保护对共享内存的操作)。
本质:一个内核中的计数器+pcb等待队列+使进程等待/唤醒的接口。
同步的实现:通过计数器对资源进行计数;计数大于0表示能获取;计数小于等于0表示不能获取通过等待接口使进程等待加入等待队列;等到有资源的时候唤醒。
互斥的实现:通过保证计数器不会大于1,保证同一时间只有一个进程能够访问资源。
5.进程间通信相关命令
ipcs //查看进程间通信资源
-m //查看共享内存
-q //查看消息队列
-s //查看信号量
ipcrm //删除进程间通信资源
-m