4.1进程间通信2015/7/25

时间:2021-04-26 14:30:10

进程间的通信都是利用内核里面的一块共享空间来通信,至于这块空间如何建立,就看看方法了

piep管道

管道是一种最基本的IPC机制,由pipe函数创建
有血缘关系进程之间的通信
调用pipe函数是在内核中开辟一块缓冲区(成为管道)用于通信,一端是读端,一端是写端,通过filedes参数传输给用户程序两个文件描述符,filedes[0]指向管道的读端,filedes[1]是指向管道的写端。一般用于父子进程之间,通过fork来传递,注意,读的一端要把写的文件描述符关掉,反之亦然,为了防止干扰。
#include <unistd.h>
int pipe(int filedes[2]);

RETURN VALUE
On success, zero is returned. On error, -1 is returned, and errno is set appropriately.

创建的管道空间是一个64k大小的环形队列,单通的。

例子:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

void sys_err(const char *str)
{
perror(str);
exit(0);
}

int main(void)
{
pid_t pid;
int fd[2];
char buf[100];

if(pipe(fd) < 0) //创建管道
sys_err("pipe");

if((pid = fork()) < 0) //创建子进程
sys_err("fork");
else if(pid > 0) { //父进程
close(fd[0]); //关闭读终端
int i = 0;
while(1) {
sprintf(buf, "I am parent %d\n", i++);
write(fd[1], buf, strlen(buf)); //向管道里写文件
sleep(1);
}
close(fd[1]);
}
else { //子进程
close(fd[1]); //关闭写终端
int len;
while(1) {
len = read(fd[0], buf, sizeof(buf)); //从管道读信息
write(STDOUT_FILENO, buf, len); //打印到屏幕上
sleep(1);
}
close(fd[0]);
}
return 0;
}

管道中的四种情况:(假设都是阻塞I/O操作,没有设置O_NONBLOCK标志)
1,所有指向管道写端的文件描述符都关闭了(管道写端的引用计数等于零),而仍然有进程从管道中读数据,name管道中剩余的数据都被读取后,再次read会返回0,就想读到文件末尾一样。
2,如果有指向管道写端的文件描述符没有关闭,并且持有管道写端的进程也有想管道中写数据,这时候有进程从管道读端读数据,name管道中剩余的数据都被读取后,再次read会堵塞,知道管道中有数据可读完在读取数据返回。
3,如果所有指向管道读端的文件描述符都关闭了,这是有进程向管道的写端write,那么该进程会受到信号SIGPIPE,通常会导致进程异常终止。
4,如果有指向管道读端的文件描述符没有关闭,而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道读端被写满是再次write会阻塞,直到管道中有空位置了在写入数据并返回。(管道大小64K,65536个字节)。

第三种情况实例验证

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

void sys_err(const char *str)
{
perror(str);
exit(0);
}

int main(void)
{
pid_t pid;
int fd[2];
char buf[100];

if(pipe(fd) < 0) //创建管道
sys_err("pipe");

if((pid = fork()) < 0) //闯进进程
sys_err("fork");
else if(pid == 0) { //子进程
close(fd[0]);
int i = 0;
while(1) { // 不停地向管道中写数据
sprintf(buf, "I am child %d\n", i++);
write(fd[1], buf, strlen(buf));
sleep(1);
}
close(fd[1]);
}
else {
close(fd[1]);
int len;
int i = 0;
while(i < 5) { //读管道,只读5次
len = read(fd[0], buf, sizeof(buf));
write(STDOUT_FILENO, buf, len);
sleep(1);
i++;
}
close(fd[0]); //读完5次之后把读端关闭,这时候所有的读端都关闭了,那子进程还在不停地写
int stat_val; //设置stat
waitpid(pid, &stat_val, 0); //回收子进程的尸体,并且具有阻塞属性
if(WIFEXITED(stat_val)) //回收到子进程的尸体之后,通过stat_val查看子进程是怎么死的。
printf("Child exited with code %d\n", WEXITSTATUS(stat_val));
else if(WIFSIGNALED(stat_val))
printf("Child terminated abnormally, signal %d\n", WTERMSIG(stat_val));
}
return 0;
}

执行之后的

$ ./a.out 
I am child 0
I am child 1
I am child 2
I am child 3
I am child 4
Chile terminated abnormally, signal 13

通过kill -l查看信号编码对应的信号是什么(下图只是本部分),看出13就是SIGPIPE

$ 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

非阻塞管道,fcntl函数设置O_NONBLOCK标志
fpathconf(int fd, int name)测试管道缓冲区大小,——PC_PIPE_BUF

fifo有名管道

解决无血缘关系进程之间的通信。
使用这个函数需要有一个管道文件,队列,先进先出
shell命令:mkfifo pathname

函数
#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);

RETURN VALUE
On success mkfifo() returns 0. In the case of an error, -1 is returned (in which case, errno is set appropriately).
当只写打开FIFO管道时,如果FIFO没有读端打开时,则open写打开会阻塞。
FIFO内核实现时可以支持双通信。(pipe单向通信,因为父子进程共享同一个file结构体)
FIFO可以一个读端,多个写端;也可以一个写端,多个读端

例,现在本目录中建立一个fifo_shared管道文件,命令行和函数都可以
写端的代码fifo_w.c

#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char *argv[])
{
int fd;
char buff[100];
int i = 0;

if(argc < 2) {
printf("./a.out fifo_shared\n");
return 0;
}

fd = open(argv[1], O_RDWR); //打开管道文件

while(1) {
sprintf(buff, "I am writer%d\n", i++);
write(fd, buff, strlen(buff)); //向管道文件中写数据
sleep(1);
}

close(fd);
return 0;
}

写端的代码fifo_r.c

#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char *argv[])
{
int fd;
char buff[100];
int len;

if(argc < 2) {
printf("./a.out fifo_shared\n");
return 0;
}

fd = open(argv[1], O_RDONLY); //打开管道文件

while(1) {
len = read(fd, buff, sizeof(buff)); //从管道文件中读数据
write(STDOUT_FILENO, buff, len);
sleep(1);
}

close(fd);
return 0;
}

内存共享映射mmap/munmap

mmap可以把磁盘文件中的一部分直接映射到内存,这样文件中的位置直接就有对应的内存地址,对文件的读写可以直接用指针来做而不需要read/write函数
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *addr, size_t length);
如果addr参数为NULL,内核会自己在进程地址空间中选择合适的地址建立映射。如果addr不是NULL,就给填一个地址。len参数是需要映射的那一部分文件的长度。off参数是从文件的什么位置开始映射,必须是也大小的整数倍(在32位系统结构上通常是4K)
prot参数有4种取值:
PROT_EXEC表示映射的这一段可执行,例如映射共享库
PROT_READ表示映射的这一段可读
PROT_WRITE表示映射的这一段可写
PROT_NONE表示映射的这一段不可访问
flag参数有很多种取值,这里只讲两种
MAP_SHARED多个进程对同一个文件的映射是共享的,一个进程对映射的内存做了修改,另一个进程也会看到这种变化。
MAP_PRIVATE多个进程对同一个文件的映射不是共享,一个进程对映射的内存做了修改,另一个进程并不会看到这种变化,也不会真的写到文件中去。

如果mmap成功则会返回映射首地址,如果出错则返回常数MAP_FAILED。当进程终止时,该进程的映射内存会自动解除,也可以调用munmap结束映射。munmap成功返回0。出错返回-1。

例子 首先在目录下建一个file_shared的普通文件
写端mmap_w.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>

struct STU { //定义结构体
int id;
char name[10];
char sex;
};

void sys_err(const char *str)
{
perror(str);
exit(0);
}

int main(int argc, char *argv[])
{
int fd;
char *buff[100];
struct STU student = {20, "xiangli", 'f'};
struct STU *mm;


if(argc < 2) {
printf("./a.out file_shared\n");
return -1;
}

fd = open(argv[1], O_RDWR); //打开文件
if(fd < 0)
sys_err("open");
if(lseek(fd, sizeof(student) -1, SEEK_SET) < 0) //把文件往后指STU这个结构体减一个字节
sys_err("lseek");
if(write(fd, "\0", 1) < 0) //然后在写入一个零,这是为了让文件中有STU这个结构体这么大的空间
sys_err("write");

mm = mmap(NULL, sizeof(student), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
//把文件映射到内存中去,NULL是让系统分配空间,空间是一个STU结构体大小,拥有可读可写权限,是共享的
if(mm == MAP_FAILED) //判断是否映射成功
sys_err("mmap");

close(fd); //关闭文件
while(1) {
memcpy(mm,&student, sizeof(student)); //向共享空间里面写数据
student.id++;
sleep(1);
}

munmap(NULL, sizeof(student)); //关闭共享空间

return 0;
}

读端mmap_r.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>

struct STU {
int id;
char name[10];
char sex;
};

void sys_err(const char *str)
{
perror(str);
exit(0);
}

int main(int argc, char *argv[])
{
int fd;
char *buff[100];
struct STU student;
struct STU *mm;


if(argc < 2) {
printf("./a.out file_shared\n");
return -1;
}

fd = open(argv[1], O_RDONLY); //因为是读数据,就只有读权限就行
if(fd < 0)
sys_err("open");

mm = mmap(NULL, sizeof(student), PROT_READ, MAP_SHARED, fd, 0); //因为是读数据,就只有读权限就行
if(mm == MAP_FAILED)
sys_err("mmap");

close(fd);
while(1) {
printf("id = %d, name = %s, sex = %c\n", mm->id, mm->name, mm->sex); //输出每次从文件中得到的数据
sleep(1);
}

munmap(NULL, sizeof(student)); //关闭共享空间

return 0;
}