Linux下进程间通信方式及实例

Linux下进程间通信方式及实例

IPC概念

IPC:InterProcess Communication 进程间通信,通过内核提供的缓冲区进行数据交换的机制。

IPC通信方式:

  • pipe 管道 – 简单
  • FIFO 有名管道
  • mmap 文件映射共享IO – 速度最快
  • 本地socket 最稳定
  • 信号 携带信息量最小
  • 共享内存
  • 消息队列

管道

管道:半双工通信

管道函数

int pipe(int pipefd[2])

  • pipefd读写文件描述符,0-代表读,1-代表写
  • 返回值:失败返回-1,成功返回0
#include <stdio.h>
#include <unistd.h>

int main(){
    int fd[2];
    pipe(fd);
    pid_t pid = fork();
    
    if (pid == 0){
        write(fd[1], "hello", 5);
    }else if (pid > 0){
        char buf[12];
        int ret = read(fd[0], buf, sizeof(buf));
        if (ret > 0){
            write(STDOUT_FILENO, buf, ret);
        }
    }
}

小例子

父子进程实现pipe通信,实现ps aux|grep bash功能

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

int main(){
    int fd[2];
    pipe(fd);
    
    pid_t pid = ford();
    if (pid == 0){
        //son -> ps
        //关闭读端
        close(fd[0]);
        //重定向
        dup2(fd[1], STDOUT_FILENO);//标准输出重定向到管道写端
        //execlp
        execlp("ps", "ps", "aux", NULL);
    }else if (pid > 0){
        //关闭写端
        close(fd[1]);
        //重定向:标准输入->管道读端
        dup2(fd[0], STDIN_FILENO);
        execlp("grep", "grep", "bash", NULL);
    }
    return 0;
}

读管道

  • 写端全部关闭 – read读到0,相当于读到文件末尾
  • 写端没有全部关闭
    • 有数据 – read读到数据
    • 没有数据 – read阻塞 fcntl函数可以更改非阻塞

写管道

  • 读端全部关闭 – 产生一个信号 SIGPIPE ,程序异常终止
  • 读端没有全部关闭
    • 管道已满 – write阻塞
    • 管道未满 – write正常写入

管道的优劣

优点:简单,相比信号,套接字实现进程间通信,简单很多

缺点:1、只能单向通信,双向通信需要建立两个管道。2、只能用于父子、兄弟进程(有共同祖先)间通信。该问题后来使用FIFO有名管道解决。

FIFO

FIFO常被称为命名管道,以区分管道(pipe)。管道(pipe只能用于“有血缘关系”的进程间。但通过FIFO,不相关的进程也能交换数据。

FIFO是unix基础文件类型中的一种。但,FIFO文件在磁盘上没有数据块,仅仅用来标识内核中一条通道。备进程可以打开这个文件进行 read/write,实际上是在读写内核通道,这样就实现了进程间通信。

创建管道

  • 使用命令:mkfifo myfifo
  • 使用函数:int mkfifo(const char *pathname, mode_t mode); 成功:0;失败:-1

内核会针对fifo文件开辟一个缓冲区,操作FIFO文件,可以操作缓冲区,实现进程通信。

一旦使用mkfifo创建了一个FIFO,就可以使用open打开它,常见的文件IO函数都可以用于FIFO。如:closereadwriteunlink

写端进程:

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

int main(int argc, char * argv[]){
    
    if (argc != 2){
        printf("./a.out fifoname\n");
        return -1;
    }
    //若当前目录有一个myfifo文件
    //打开FIFO文件
    int fd = open(argv[1], O_WRONLY);
    //写
    char buf[256];
    int num = 1;
    while(1){
        memset(buf, 0x00, sizeof(buf));
        sprintf(buf, "xiaoming%04d", num++);
        write(fd, buf, strlen(buf));
        sleep(1);
	}
    //关闭
    close(fd);
    return 0;
}

读端进程:

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

int main(itn argc, char* argv[]){
    if (argc != 2){
        printf("./a.out fifoname\n");
        return -1;
    }
    int fd = open(argv[1], O_RDONLY);
    char buf[256];
    int ret;
    while(1){
        memset(buf, 0x00, sizeof(buf));
        ret = read(fd, buf, sizeof(buf));
        if (ret > 0) 
            printf("read:%s\n", buf);
	}
    close(fd);
    return 0;
}

注意事项:打开FIFO文件的时候,read端会阻塞等待write端打开open,write端同理,也会阻塞等待另外一端打开。

mmap映射

mmap函数

创建映射区

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

  • addr : 映射地址,可以传NULL
  • length:映射区的长度
  • prot:
    • PROT_EXEC pages may be executed.
    • PROT_READ pages may be read.
    • PROT_WRITE pages may be written.
    • PROT_NONE pages may not be accessed.
  • flags:
    • MAP_SHARED 映射区是共享的,对内存的修改会影响到源文件
    • MAP_PRIVATE 映射区是私有的
  • fd:文件描述符,open打开一个文件
  • offset:偏移量
  • 返回值
    • 成功:返回可用内存首地址
    • 失败:返回MAP_FAILED

释放映射区

int munmap(void *addr, size_t length);

  • addr:传mmap的返回值
  • length:mmap创建长度
  • 返回值:
    • 成功:0
    • 失败:-1

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

int main(){
    int fd = open("mem.txt", O_RDWR);
    //创建映射区
    char *mem = mmap(NULL, 8, PROT_READ|PROT_WRITE,MAP_SHARED, fd, 0);
    if (mem == MAP_FAILED){
        perror("mmap err");
        return -1;
    }
    //拷贝数据
    strcpy(mem, "hello");
    //释放mmap
    munmap(mem,8);
    close(fd);
	return 0;
}

mmap九问

  1. 如果更改mem变量的地址,释放的时候 munmap,传入mem还能成功吗?

    不能

  2. 如果对mem越界操作会怎么样?

    文件的大小对映射区操作有影响,尽量避免

  3. 如果文件偏移量随便填个数会怎么样?

    offset必须是4k的整数倍

  4. 如果文件描述符先关闭,对mmap映射有没有影响?

    没有影响

  5. open的时候,可以新创建一个文件来创建映射区吗?

    不可以用大小为0的文件,文件大小为0会引起bus error

  6. open文件选择 O_WRONLY,可以吗?

    不可以:Permission Denied,因为把文件映射到缓冲区会进行一次读操作,若没有读操作的权限会报错。

  7. 当选择 MAP_SHARED的时候,open文件选择 O_RDONLY,pot可以选择PROT_READ|PROT_ WRITE吗?

    不可以,同样会报Permission Denied,SHARED的时候,映射区的权限 <= open文件的权限

  8. mmap什么情况下会报错?

    很多情况

  9. 如果不判断返回值会怎么样?

进程通信例

父子进程通信

子进程

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

int main(){
    int fd = open("mem.txt", O_RDWR);
    //创建映射区
    int *mem = mmap(NULL, 4, PROT_READ|PROT_WRITE,MAP_SHARED, fd, 0);
    if (mem == MAP_FAILED){
        perror("mmap err");
        return -1;
    }
    //fork子进程
	pid_t pid = fork();
    //父进程和子进程交替修改数据
    if (pid == 0){
        //son
        *mem = 100;
        printf("child, *mem = %d\n", *mem);
        sleep(3);
        printf("child, *mem = %d\n", *mem);
    }else if (pid > 0){
        //parent
        sleep(1);
        printf("parent, *mem = %d\n", *mem);
        *mem = 1001;
        printf("parent, *mem = %d\n", *mem);
        wait(NULL);
    }
	munmap(mem,4);
    close(fd);
	return 0;
}

无血缘关系进程通信

进程1:修改映射区

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

typedef struct _Student{
    int sid;
    char sname[20];
}Student;

int main(int argc, char *argv[]){
    if (argc != 2){
        printf("./a.out fifoname\n");
        return -1;
    }
    int fd = open(argv[1], O_RDWR|O_CREAT|O_TRUNC, 0666);
    int length = sizeof(Student);
    ftruncate(fd, length);
    //创建映射区
    Student *stu = mmap(NULL, length, PROT_READ|PROT_WRITE,MAP_SHARED, fd, 0);
    if (mem == MAP_FAILED){
        perror("mmap err");
        return -1;
    }
    int num = 1;
    while (1){
        stu->sid = num;
        sprintf(stu->sname, "xiaaoming-%03d", num++);
        sleep(1);	//每隔一秒修改映射区内容
    }
	munmap(mem,length);
    close(fd);
	return 0;
}

进程2:读取数据

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

typedef struct _Student{
    int sid;
    char sname[20];
}Student;

int main(int argc, char *argv[]){
    if (argc != 2){
        printf("./a.out fifoname\n");
        return -1;
    }
    int fd = open(argv[1], O_RDWR);
    int length = sizeof(Student);
    //创建映射区
    Student *stu = mmap(NULL, length, PROT_READ|PROT_WRITE,MAP_SHARED, fd, 0);
    if (mem == MAP_FAILED){
        perror("mmap err");
        return -1;
    }
    while (1){
        printf("sid = %d, sname = %s\n", stu->sid, stu->sname);
        sleep(1);
    }
	munmap(mem,length);
    close(fd);
	return 0;
}

匿名映射

通过使用我们发现,使用映射区来完成文件读写操作十分方便,父子进程间通信也较容易。但缺陷是,每次创建映射区一定要依赖一个文件才能实现。通常为了建立映射区要open一个temp文件,创建好了再 unlink、cose掉,比较麻烦。可以直接使用匿名映射来代替。

其实Linux系统给我们提供了创建匿名映射区的方法,无需依赖一个文件即可创建映射区。同样需要借助标志位参数fags来指定。

使用 MAP_ANONYMOUS(或 MAP_ANON),如:

int *p=mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);

"4"随意举例,该位置表大小,可依实际需要填写

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

int main(){
    //创建映射区
    int *mem = mmap(NULL, 4, PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON, -1, 0);
    if (mem == MAP_FAILED){
        perror("mmap err");
        return -1;
    }
    //fork子进程
	pid_t pid = fork();
    //父进程和子进程交替修改数据
    if (pid == 0){
        //son
        *mem = 100;
        printf("child, *mem = %d\n", *mem);
        sleep(3);
        printf("child, *mem = %d\n", *mem);
    }else if (pid > 0){
        //parent
        sleep(1);
        printf("parent, *mem = %d\n", *mem);
        *mem = 1001;
        printf("parent, *mem = %d\n", *mem);
        wait(NULL);
    }
	munmap(mem,4);
    close(fd);
	return 0;
}

需注意的是, MAP ANONYMOUS和 MAP ANON这两个宏是Lnux操作系统特有的宏

这两个宏在有些Unix系统没有,可以用/dev/zero做映射

信号

信号的概念

信号在我们的生活中随处可见,如:古代战争中摔杯为号;现代战争中的信号弹;体育比赛中使用的信号枪…他们都有共性:1.简单 2.不能携带大量信息 3.满足某个特设条件才发送

信号是信息的载体, Linux/UNⅨ环境下,古老、经典的通信方式,现下依然是主要的通信手段。

Unix早期版丰就提供了信号机制,但不可靠,信号可能失 Berkeley和A&T都对信号模型做了更改,增加了可靠信号机制。但彼此不兼容。pOS1对可靠信号例程进行了标准化。

信号的机制

A给B发送信号,B收到信号之前执行自己的代码,收到信号后,不管执行到程序的什么位置,都要暂停运行,去处理信号,处理完毕再继续执行。与硬件中断似——异步模式

但信号是软件层面上实现的中断,早期常被称为“软中断”。

信号的特质:由于信号是通过软件方法实现,其实现手段导致信号有很强的延时性。但对于用户来说,这个廷迟时间非常短,不易察觉

每个进程收到的所有信号,都是由内核负责发送的,内核处理

共享内存

创建共享内存

用到的函数shmget, shmat, shmdt

函数名功能描述
shmget创建共享内存,返回pic key(共享内存id)
shmat第一次创建完共享内存时,它还不能被任何进程访问,shmat()函数的作用就是用来启动对指定id的共享内存的访问,并把共享内存连接到当前进程的地址空间
shmdt该函数用于将共享内存从当前进程中分离。注意,将共享内存分离并不是删除它,只是使该共享内存对当前进程不再可用。

函数原型如下:

int shmget(key_t key, size_t size, int flag);
key: 标识符的规则
size:共享存储段的字节数
flag:读写的权限
返回值:成功返回共享存储的id,失败返回-1
//
void *shmat(int shmid, const void *addr, int flag);
shmid:共享存储的id
addr:一般为0,表示连接到由内核选择的第一个可用地址上,否则,如果flag没有指定SHM_RND,则连接到addr所指定的地址上,如果flag为SHM_RND,则地址取整
flag:如前所述,一般为0
返回值:如果成功,返回共享存储段地址,出错返回-1
//
int shmdt(void *addr);
addr:共享存储段的地址,以前调用shmat时的返回值
//
int shmctl(int shmid,int cmd,struct shmid_ds *buf)
shmid:共享存储段的id
cmd:一些命令

	IPC_STAT 得到共享内存的状态
        IPC_SET 改变共享内存的状态
        IPC_RMID 删除共享内存 

IPC_RMID 命令实际上不从内核删除一个段,而是仅仅把这个段标记为删除,实际的删除发生在最后一个进程离开这个共享段时。 
#include <stdio.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>

int main(){
  int shm;
  char* ptr;

  shm = shmget(IPC_PRIVATE, 129, IPC_CREAT | 0600);
  if(shm < 0){
    perror("shmget");
    return 1;
  }

  ptr = (char*)shmat(shm, NULL , 0);
  if(atoi(ptr) == -1){
    perror("shmat");
    return -1;
  }
  strcpy(ptr, "HELLO");

  shmdt(ptr);

  return 0;
}

访问共享内存

#include <stdio.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>

int main(int argc, char* argv[]){
  int shm;
  char* ptr;

  if(argc != 2){
    return 1;
  }

  shm = atoi(argv[1]);

  ptr = (char*)shmat(shm, NULL, 0);
  if(atoi(ptr) == -1){
    perror("shmat");
    return 1;
  }

  printf("string from shared memory : %s\n", ptr);

  shmdt(ptr);

  return 0;
}

删除共享内存

#include <stdio.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>

int main(int argc, char* argv[]){
  int shm;
  shmid_ds sds;

  if(argc != 2){
    printf("argc is wrong");
    return 1;
  }

  shm = atoi(argv[1]);

  if(shmctl(shm, IPC_RMID, &sds) != 0){
    perror("shmctl");
    return 1;
  }

  return 0;
}

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值