进程间通讯(IPC机制) 管道 信号量 共性内存 消息队列 详细图解

进程间通讯-IPC机制

  • 常用命令
  • 管道
    • 有名管道读写编程
      • 有名管道示意图
    • 无名管道
  • 信号量
    • 信号量的概念
    • 信号量接口函数
    • 进程 a 和进程 b 模拟访问打印机 用信号量互斥
      • 画图分析
      • 代码实现
      • 测试结果
      • 显示和操作 共享内存 信号量 消息队列 的命令
  • 共享内存
    • 共享内存定义
    • 共享内存函数接口
    • 实例编程
      • 图示理解
      • 编码实现
      • 测试结构
  • 消息队列
    • 图示理解
    • 接口函数
    • 实例编程

常用命令

进程间通讯(IPC机制):管道 信号量 共享内存 消息队列 套接字
查看进程的命令:
在一个终端执行

sleep(200)

在另一个终端查看该进程

ps -ef | grep "sleep"

显示和操作 共享内存 信号量 消息队列 的命令

ipcs

删除该信号量

ipcrm -s 2

删除共享内存

ipcrm -m 22201

管道

有名管道
管道类型的文件大小都为0
有名管道:任意进程
无名管道:父子进程

有名管道读写编程

#include
#include
#include
#include
#include
//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
#include
#include
#include
#include
//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);
}

进程间通讯(IPC机制) 管道 信号量 共性内存 消息队列 详细图解_第1张图片
注意:
如果直接关闭读端(ctrl+c),只剩写端,写端write写数据触发异常,写入数据就会异常终止 会受到该信号SIGPIPE
如果关闭写端(end 后者ctrl+c),读端会自动检测,读端read返回值为0,也会关闭

有名管道示意图

进程间通讯(IPC机制) 管道 信号量 共性内存 消息队列 详细图解_第2张图片

内核中管道的实现:
进程间通讯(IPC机制) 管道 信号量 共性内存 消息队列 详细图解_第3张图片
头指针负责写入,尾指针负责读,一个追上另一个,要么管道为满,要么为空,
管道为空时,读操作阻塞
管道为满,写操作阻塞

无名管道

没有名字,只能在父子进程之间使用,创建无无名管道

int pipe(int pipefd[2]);
#include
#include
#include
#include
#include
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
#include
#include
#include
#include
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),如图所示
进程间通讯(IPC机制) 管道 信号量 共性内存 消息队列 详细图解_第4张图片

代码实现

sem.h

#include 
#include 
#include 
#include 
#include 

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 
#include 
#include 
#include 
#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 
#include 
#include 
#include 
#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里销毁

测试结果

进程间通讯(IPC机制) 管道 信号量 共性内存 消息队列 详细图解_第5张图片

显示和操作 共享内存 信号量 消息队列 的命令

ipcs

进程间通讯(IPC机制) 管道 信号量 共性内存 消息队列 详细图解_第6张图片
进程间通讯(IPC机制) 管道 信号量 共性内存 消息队列 详细图解_第7张图片
信号量用完一定要销毁,如果么有销毁,下次用如果用同一个信号量的话(id值相同),原本我全新创建,我要赋初值为10,但是已经存在了,获取值可能不是我所期望的,因此用完一定移除,万一在程序中出现问题,异常结束,可以用下面的命令
删除该信号量

ipcrm -s 2

删除共享内存

ipcrm -m 22201

共享内存

共享内存定义

共享内存是先在物理内存上申请一块空间,多个进程可以将其映射到自己的虚拟地址空间中。所有进程都可以访问共享内存中的地址,就好像它们是由malloc分配的一样。如果某个进程向共享内存写入了数据,所做的改动将立刻被可以访问同一段共享内存的任何其他进程看到。
进程间通讯(IPC机制) 管道 信号量 共性内存 消息队列 详细图解_第8张图片

共享内存函数接口

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执行完毕后,才能再次执行
进程间通讯(IPC机制) 管道 信号量 共性内存 消息队列 详细图解_第9张图片

编码实现

//main.c

#include 
#include 
#include 
#include 
#include 
#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 
#include 
#include 
#include 
#include 
#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 
#include 
#include 
#include 
#include 
#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();

测试结构

进程间通讯(IPC机制) 管道 信号量 共性内存 消息队列 详细图解_第10张图片
进程间通讯(IPC机制) 管道 信号量 共性内存 消息队列 详细图解_第11张图片

消息队列

图示理解

进程间通讯(IPC机制) 管道 信号量 共性内存 消息队列 详细图解_第12张图片

接口函数

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 
#include 
#include 
#include 
#include 

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 
#include 
#include 
#include 
#include 

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;
}

你可能感兴趣的:(Linux,linux,运维,服务器)