进程间通讯-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;
}
                



![Javascript 数据结构[入门]](https://img-blog.csdnimg.cn/083e22ef77cd4326a05791a0b41e9c51.png)














