进程间通讯-IPC机制
- 常用命令
- 管道
- 有名管道读写编程
- 有名管道示意图
- 无名管道
- 信号量
- 信号量的概念
- 信号量接口函数
- 进程 a 和进程 b 模拟访问打印机 用信号量互斥
- 画图分析
- 代码实现
- 测试结果
- 显示和操作 共享内存 信号量 消息队列 的命令
- 共享内存
- 共享内存定义
- 共享内存函数接口
- 实例编程
- 图示理解
- 编码实现
- 测试结构
- 消息队列
- 图示理解
- 接口函数
- 实例编程
常用命令
进程间通讯(IPC机制):管道 信号量 共享内存 消息队列 套接字
查看进程的命令:
在一个终端执行
sleep(200)
在另一个终端查看该进程
ps -ef | grep "sleep"
显示和操作 共享内存 信号量 消息队列 的命令
ipcs
删除该信号量
ipcrm -s 2
删除共享内存
ipcrm -m 22201
管道
有名管道
管道类型的文件大小都为0
有名管道:任意进程
无名管道:父子进程
有名管道读写编程
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
//write.c
int main(){
int fdw = open( "fifo" ,0_WRONLY);
if ( fdw == -1 )
{
printf( "open err\n ");
exit(1);
}
while( 1 ){
char buff[128] ={0};printf( "input: \n");
fgets(buff,128,stdin);
if ( strncmp(buff , "end" ,3) == 0 ){ break; }
write(fdw , buff,strlen( buff));
}
close( fdw);
}
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
//read.c
int main(){
int fdr = open( "fifo" ,O_RDONLY);
if ( fdr == -1 )
{
printf( "open err\n ");
exit(1);
}
while( 1 ){
char buff[128] ={0};
int n=read(fdr ,buff,127);//如果管道没有数据,就会阻塞
if(n==0)
{
printf( "it close pipe write terminal\n")
break;
}
printf( " buff=(%d)=%s\n ",n,buff);
}
close(fdr);
}
注意:
如果直接关闭读端(ctrl+c),只剩写端,写端write写数据触发异常,写入数据就会异常终止 会受到该信号SIGPIPE
如果关闭写端(end 后者ctrl+c),读端会自动检测,读端read返回值为0,也会关闭
有名管道示意图
内核中管道的实现:
头指针负责写入,尾指针负责读,一个追上另一个,要么管道为满,要么为空,
管道为空时,读操作阻塞
管道为满,写操作阻塞
无名管道
没有名字,只能在父子进程之间使用,创建无无名管道
int pipe(int pipefd[2]);
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
int main(){
int fd[2];//操作管道描述符,fd[0],r,fd[1],w
if(pipe(fd)==-1){//
printf("pipe err\n");
exit(0);
}
write(fd[1],"hello",5);
sleep(5);
char buff[128]={0};
read(fd[0],buff,128);
printf("buff=%s\n",buff);
close(fd[0]);
close(fd[1]);
exit(0);
}
父子进程一个读一个写,让子进程读,父进程写
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
int main(){
int fd[2];//操作管道描述符,fd[0],r,fd[1],w
if(pipe(fd)==-1){
printf("pipe err\n");
exit(0);
}
pid_t pid = fork();
if ( pid == -1 ){
exit(0);
}
if ( pid == 0 ){
close(fd[1]);
char buff[128] = {0};
read(fd[0],buff,127);
printf("child buff=%s\[n" ,buff);close(fd[0]);
}
else{
close(fd[0]);
write(fd[1], "hello" ,5);
close(fd[1]);
}
exit(0);
}
无名管道是一个半双工
半双工 对讲机,能互相发送,但不能同时发
全双工:打电话,信息能同时相互传递
单共,收音机只能接收信号,不能发送信号,单向
写入管道的数据在内存,有名无名都是
有名和无名管道的区别:有名管道可以在任意进程间通讯,而无名只能在父子进程间通讯
信号量
信号量的概念
信号量是一个特殊的变量,一般取正数值。它的值代表允许访问的资源数目,获取资源时,需要对信号量的值进行原子减一,该操作被称为 P 操作。当信号量值为 0 时,代表没有资源可用,P 操作会阻塞。释放资源时,需要对信号量的值进行原子加一,该操作被称为 V
操作。信号量主要用来同步进程。信号量的值如果只取 0,1,将其称为二值信号量。如果信号量的值大于 1,则称之为计数信号量。
临界资源:同一时刻,只允许被一个进程或线程访问的资源
临界区:访问临界资源的代码段
信号量接口函数
int semget(key_t key, int nsems, int semflg);
semget()创建或者获取已存在的信号量
semget()成功返回信号量的 ID, 失败返回-1
key:两个进程使用相同的 key 值,就可以使用同一个信号量
nsems:内核维护的是一个信号量集,在新建信号量时,其指定信号量集中信号
量的个数
semflg 可选: IPC_CREAT IPC_EXCL
int semop(int semid, struct sembuf *sops, unsigned nsops);
semop()成功返回 0,失败返回-1
这里要定义一个结构体
struct sembuf
{
unsigned short sem_num; //指定信号量集中的信号量下标 为0 1 ...
short sem_op; //其值为-1,代表 P 操作,其值为 1,代表 V 操作
short sem_flg; //SEM_UNDO
};
int semctl(int semid, int semnum, int cmd, ...);
semctl()控制信号量,初始化和销毁需要使用
semctl()成功返回 0,失败返回-1
semnum 信号量集中对应信号量的下标
cmd 选项: SETVAL IPC_RMID
信号量id是一个信号量集的id,里面可能 有多个信号量,用下标区分
semun是一个联合体
union semun
{
int val;
...
}
进程 a 和进程 b 模拟访问打印机 用信号量互斥
画图分析
进程 a 和进程 b 模拟访问打印机,进程 a 输出第一个字符‘a’表示开始使用打印
机,输出第二个字符‘a’表示结束使用,b 进程操作与 a 进程相同。(由于打印机同一时刻
只能被一个进程使用,所以输出结果不应该出现 abab),如图所示
代码实现
sem.h
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/sem.h>
union semun
{
int val;
};
void sem_init();
void sem_p();
void sem_v();
void sem_destory();
sem.c
#include "sem.h"
static int semid = -1;
void sem_init()
{
semid = semget((key_t)1234, 1, IPC_CREAT | IPC_EXCL | 0600);
if (semid == -1)
{
semid = semget((key_t)1234, 1, 0600);
if (semid == -1)
{
printf("semget err\n");
}
}
else
{
union semun a;
a.val = 1;
if (semctl(semid, 0, SETVAL, a) == -1)
{
printf("set val err\n");
}
}
}
IPC_CREAT | IPC_EXCL 创建一个新的信号量,如果已经有的话,就会失败,直接获取就行。
semctl(semid, 0, SETVAL, a) 将信号量集中第一个信号量的初始值设置为a.val
void sem_p()
{
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = -1;
buf.sem_flg = SEM_UNDO;
if (semop(semid, &buf, 1) == -1)
{
printf("p err\n");
}
}
void sem_v()
{
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = 1;
buf.sem_flg = SEM_UNDO;
if (semop(semid, &buf, 1) == -1)
{
printf("v err\n");
}
}
void sem_destory()
{
if (semctl(semid, 0, IPC_RMID) == -1)
{
printf("destroy err\n");
}
}
semctl(semid, 0, IPC_RMID)销毁信号量
a.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "sem.h"
int main()
{
sem_init();
for (int i = 0; i < 5; i++)
{
sem_p();
printf("A");
fflush(stdout);
int n = rand() % 3;
sleep(n);
printf("A");
fflush(stdout);
sem_v();
n = rand() % 3;
sleep(n);
}
}
b.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "sem.h"
int main()
{
sem_init();
for (int i = 0; i < 5; i++)
{
sem_p();
printf("B");
fflush(stdout);
int n = rand() % 3;
sleep(n);
printf("B");
fflush(stdout);
sem_v();
n = rand() % 3;
sleep(n);
}
sleep(10);//
sem_destory();
}
sleep(10);只有一个信号量,所以只能销毁一次,那就睡眠10s,b后结束,在b里销毁
测试结果
显示和操作 共享内存 信号量 消息队列 的命令
ipcs
信号量用完一定要销毁,如果么有销毁,下次用如果用同一个信号量的话(id值相同),原本我全新创建,我要赋初值为10,但是已经存在了,获取值可能不是我所期望的,因此用完一定移除,万一在程序中出现问题,异常结束,可以用下面的命令
删除该信号量
ipcrm -s 2
删除共享内存
ipcrm -m 22201
共享内存
共享内存定义
共享内存是先在物理内存上申请一块空间,多个进程可以将其映射到自己的虚拟地址空间中。所有进程都可以访问共享内存中的地址,就好像它们是由malloc分配的一样。如果某个进程向共享内存写入了数据,所做的改动将立刻被可以访问同一段共享内存的任何其他进程看到。
共享内存函数接口
int shmget(key_t key,size_t size, int shmflg) ;
shmget()用于创建或者获取共享内存
shmget()成功返回共享内存的 ID, 失败返回-1
key: 不同的进程使用相同的 key 值可以获取到同一个共享内存
size: 创建共享内存时,指定要申请的共享内存空间大小
shmflg: IPC_CREAT IPC_
只显示共享内存
ipcs -m
映射
void* shmat(int shmid, const void *shmaddr, int shmflg);
shmat()将申请的共享内存的物理内存映射到当前进程的虚拟地址空间上
shmat()成功返回返回共享内存的首地址,失败返回 NULL
shmaddr:一般给 NULL,由系统自动选择映射的虚拟地址空间
shmflg: 一般给 0, 可以给 SHM_RDONLY 为只读模式,其他的为读写
int shmdt(const void *shmaddr);
shmdt()断开当前进程的 shmaddr 指向的共享内存映射
shmdt()成功返回 0, 失败返回-1
实例编程
图示理解
ps初始值为1 ps2初始值为0 ,ps1通过,将数据写到共享内存中,再执行vs2,通知test可以进行数据的打印,ps2可以通过,进行打印,打印结束后vs1,通知main可以进行输入,main执行一次后,只有test执行完毕后,才能再次执行
编码实现
//main.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/shm.h>
#include "sem.h"
int main()
{
int shmid = shmget((key_t)1234, 128, IPC_CREAT | 0600);
if (shmid == -1)
{
printf("shmget err\n");
exit(1);
}
char *s = (char *)shmat(shmid, NULL, 0);
if (s == (char *)-1)
{
printf("shmat err\n");
exit(1);
}
sem_init();
while (1)
{
printf("input:\n");
char buff[128] = {0};
fgets(buff, 128, stdin);
sem_p(SEM1);
strcpy(s, buff);
sem_v(SEM2);
if (strncmp(buff, "end", 3) == 0)
{
break;
}
}
shmdt(s);
}
test.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/shm.h>
#include "sem.h"
int main()
{
int shmid = shmget((key_t)1234, 128, IPC_CREAT | 0600);
if (shmid == -1)
{
printf("shmget err\n");
exit(1);
}
char *s = (char *)shmat(shmid, NULL, 0);
if (s == (char *)-1)
{
printf("shmat err\n");
exit(1);
}
sem_init();
while (1)
{
sem_p(SEM2);
if (strncmp(s, "end", 3) == 0)
{
break;
}
printf("s=%s\n", s);
sem_v(SEM1);
}
shmdt(s);
sem_destroy();
shmctl(shmid, IPC_RMID, NULL);
}
sem.c
#include "sem.h"
static int semid = -1;
void sem_init()
{
semid = semget((key_t)1234, SEM_NUM, IPC_CREAT | IPC_EXCL | 0600);
if (semid == -1)
{
semid = semget((key_t)1234, SEM_NUM, 0600);
if (semid == -1)
{
printf("semget err\n");
return;
}
}
else
{
int arr[SEM_NUM] = {1, 0};
for (int i = 0; i < SEM_NUM; i++)
{
union semun a;
a.val = arr[i];
if (semctl(semid, i, SETVAL, a) == -1)
{
printf("semctl setval err\n");
}
}
}
}
void sem_p(enum SEM_INDEX index)
{
struct sembuf buf;
buf.sem_num = index;
buf.sem_op = -1; // p
buf.sem_flg = SEM_UNDO;
if (semop(semid, &buf, 1) == -1)
{
printf("semop p err\n");
}
}
void sem_v(enum SEM_INDEX index)
{
struct sembuf buf;
buf.sem_num = index;
buf.sem_op = 1; // v
buf.sem_flg = SEM_UNDO;
if (semop(semid, &buf, 1) == -1)
{
printf("semop v err\n");
}
}
void sem_destroy()
{
if (semctl(semid, 0, IPC_RMID) == -1)
{
printf("del err\n");
}
}
sem.h
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/sem.h>
#define SEM_NUM 2
enum SEM_INDEX
{
SEM1,
SEM2
};
union semun
{
int val;
};
void sem_init();
void sem_p(enum SEM_INDEX index);
void sem_v(enum SEM_INDEX index);
void sem_destroy();
测试结构
消息队列
图示理解
接口函数
struct mess
{
long type;//1起步
char buff[32]; // user data
};
msgget()创建或者获取一个消息队列
msgget()成功返回消息队列 ID,失败返回-1
msqflg: IPC_CREAT
int msgget(key_t key, int msqflg);
int msgsnd(int msqid, const void *msqp, size_t msqsz, int msqflg);
msgsnd()发送一条消息
msgid 消息队列的id
(void*)&dt 将该消息结构体对象的地址传入队列
msqsz 消息结构体中有效数据的长度
msqflg 添加消息的时候,如果消息被放满,是阻塞,还是返回失败 默认是0 阻塞
一般设置为 0 可以设置 IPC_NOWAIT
ssize_t msgrcv(int msqid, void *msgp, size_t msqsz, long msqtyp, int msqflg);
msgrcv()接收一条消息
msgid 消息队列的id
(void*)&dt 将消息队列里面的数据读到dt结构体里
32消息结构体中buff[]的大小
msqtyp 消息类型 0 就是不区分类型
msqflg标志位 一般设置为 0 可以设置 IPC_NOWAIT
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
msgctl()控制消息队列
msgctl()成功返回 0,失败返回-1
cmd: IPC_RMID
实例编程
进程 a 发送一条消息,进程 b 读取消息
msga.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/msg.h>
struct mess
{
long type;
char buff[32]; // user data
};
int main()
{
int msgid = msgget((key_t)1234, IPC_CREAT | 0600);
if (msgid == -1)
{
exit(1);
}
struct mess dt;
dt.type = 1;
strcpy(dt.buff, "hello");
msgsnd(msgid, (void *)&dt, 32, 0);
return 0;
}
msgb.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/msg.h>
struct mess
{
long type;
char buff[32]; // user data
};
int main()
{
int msgid = msgget((key_t)1234, IPC_CREAT | 0600);
if (msgid == -1)
{
exit(1);
}
struct mess dt;
msgrcv(msgid, (void *)&dt, 32, 1, 0);
printf("read msg:%s\n", dt.buff);
return 0;
}