进程间通信

进程间通信(ipc)

哪些是进程间通信: 信号量 共享内存 消息队列 套接字 管道

通信方式:单工的:a->b,电台到收音机,单向的;

​ 半双工的:a<->b,同一时刻a要么是发出者要么是接收者

​ 全双工的:收和发是同时进行的,例如:打电话

管道

管道的特点

1.管道文件是在内存中分配空间(即运行速度快),它有读端r,写端w。

2.管道为空,读会阻塞,;管道写满,写会阻塞

3.管道的写端关闭,读端返回为0;

4.管道读端关闭,写端写入数据触发异常(内核就会通过信号(SIGPIPE)提醒你)

管道的通信方式:半双工的。

写入管道的数据在内存中。

2.作用

​ 管道可以用来在两个进程之间传递数据,如:ps-ef | grep “bash”

,其中‘|’就是管道,其作用就是将ps命令的结果写入管道文件,然后grep再用管道文件中读出该数据。

3.有名管道

有名管道可以在任意两个进程间通信

有名管道的创建:命令创建 mkfifo FIFO(管道文件名);系统调用创建

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pZpcC9zQ-1638457512197)(E:\linux jietu\屏幕截图 2021-11-14 142249.jpg)]

最前面的p为管道文件标识

写一个只往fifo文件中写入的文件。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ElEAYegt-1638457512198)(E:\linux jietu\屏幕截图 2021-11-14 143529.jpg)]

写一个只读取fifo的文件

屏幕截图 2021-11-14 144221

管道需要两个进程才能打开!!!!系统规定的

4.无名管道

无名管道主要应用于父子进程间的通信。

无名管道的创建:

#include<unistd.h>
pipe()成功返回0,失败返回-1;
    fds[0]是管道读端的描述符
    fds[1]是管道写端的描述符
    int pipe(int fds[2])

s

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<fcntl.h>
#include<signal.h>
int main()
{
    int fd[2];//fd[0]读端 fd[1]写端
    int res=pipe(fd);
    assert(res==-1);
    
    pid_t pid=fork();
    assert(pid!=-1);
    
    if(pid==0)
    {
    	close(fd[1]);
        char buff[128]={0};
        read(fd[0],buff,127);
        printf("child read:%s\n",buff);
        close(fd[0]);
    }
    else
    {
        close(fd[0]);
        char buff[128]={0};
        fgets(buff,128,stdin);
        write(fd[1],buff,strlen(buff));
        close(fd[1]);
    }
    
}

管道的实现:尾指针负责读,头指针负责写

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EKbPWhA3-1638457512199)(E:\linux jietu\屏幕截图 2021-11-14 161952.jpg)]

信号量

信号量描述:

信号量主要用来同步进程

​ 信号量是一个特殊的变量,一般取正数值。它的值代表允许访问的资源数目。

​ 获取资源时,需要对信号量的值进行原子减一,该操作被称为p操作。信号量值为0时,代表没有资源可用,p操作会阻塞。

​ 释放资源时,需要对信号量的值进行原子加一,该操作称为v操作

信号量主要用来同步进程。其值如果只取0,1,将其称为二值信号量。如果信号量的值大于1,则称之为计数信号量。

​ 临界资源:同一时刻,只允许被一个进程或线程访问的资源。

​ 临界区:访问临界资源的代码段。

同步进程:通过信号量等工具来控制程序,保证程序在同一时刻按照信号量的规定来进行资源的访问。

linux的信号量机制

所有的linux信号量函数都是针对成组的通用信号量进行操作(以便于应对一个进程需要锁定多个资源的复杂情况)。

信号量函数的定义如下:

#include<sys/sem.h>

int semctl(int sem_id, int sem_num,int command,...);

int semget(key_t key,int num_sems,int sem_flags);

int semop(int sem_id,struct sembuf *sem_ops,size_t num_Sem_ops);

注意:这些函数都是用来对成组的信号量进行操作的。

参数key,代表程序可能要使用的某个资源,如果多个程序使用相同的key值,它将负责协调工作。

sem_get()

semget函数:作用创建一个新信号量或取得一个已有信号量得健。

int semget(key_t key,int num_sems,int sem_flgas);

key:不相关的进程通过key来访问同一个信号量。

num_sems:说明需要的信号量数目,一般都是1

sem_flags :可以和IPC_CREAT做按位或操作,来创建一个新信号量。若函数用不到IPC_CREAT则会自动忽略。 一般通过联合使用标志IPC_CREAT和IPC_EXCL来确保创建出一个新的,唯一的信号量(若信号量已存在将返回错误)。

返回值:成功时返回一个正数,即其他信号量函数将用到的信号量标识符。失败返回-1。

semop()

作用:改变信号量的值。

int semop(int sem_id,struct sembuf *sem_ops,size_t num_sem_ops);

//sem_id:是由semget返回的信号量标识符
//sem_ops 是指向一个结构数组的指针,每个数组元素至少包含以下几个成员:
struct sembuf{
    short sem_num;//信号量编号,除非使用一组信号量,否则一般取0
    short sem_op;//信号量一次操作需要改变的值,-1为p操作,1为v操作。
    short sem_flg;//通常被设置为SEM_UNDO。将使得操作系统跟踪当前进程对信号量的修改情况。若这个进程没有释放该信号量就终止,系统会自动释放该进程持有的信号量。
}
num_sem_ops:一般写1
semctl()

用来直接控制信号量信息

int semctl(int sem_id,int sem_num,int command,..);
//sem_id:由semget返回的信号量标识符,
//sem_num参数是信号量编号,成组信号量才用,一般取0,(是信号量的下标,类似于数组,0,1,2这样的)
//command参数是将要采取的动作。如:
//SETVAL把信号量初始化为一个已知道的值,此值通过semun中的val成员设置
//IPC_RMID,用于删除一个已经无需继续使用的信号量标识符

若还有第四个参数,将会是一个union semun结构。
    union semun{
    int val;
    struct semid_ds *buf;
    unsigned short *array;
}//规定是自己定义,但一般会在头文件给出(semun.h)

成功返回0,失败返回-1.

例子
//三个进程A,B,C,要求输出为ABCABC的方式
//只需要三个信号量,信号量初始化为1,0,0,,A获取1信号量,释放2信号量,2获取2信号量,释放3信号量,3获取3信号量释放1信号量。
//最后在c程序后面销毁。
#define SEN_NUM 3
#define SEM1 0
#define SEM2 1
#define SEM3 2

union semun
{
    int val;
}
static int semid = -1;
//创建或者初始化信号量
int sem_init()
{
    semid=semget((key_t)1234,SEM_NUM,IPC_CREAT|IPC_EXCL|0600);//0600表示可读可写
    if(semid==-1)
    {
    	semid=semget((key_t)1234,SEM_NUM,0600);
        if(semid==-1)
        {
            printf("semget err\n")
           return -1;
        }
    }
   else
   {
        int arr[SEM_NUM]={1,0,0};// 给信号量初始化的内容
        for(int i=0;i<SEM_NUM;i++)
        {
            union semun a;
            a.val=arr[i];
            if(semctl(semid,i,SETVAL,a)==-1)
            {
                printf("init err\n");
               return -1;
            }
        }
   }
    
}
//p操作  获取资源
void sem_p(int index)//index   是下标,表示现在操作的是第几个信号量。
{
    if(index<0||index>=SEM_NUM)
    {
        return;
    }
    
    struct sembuf buf;
    buf.sem_num=index;
    buf.sem_op=-1//p;
    buf.sem_flg=SEM_UNDO;
    
    if(semop(semid,&buf,1)==-1)
    {
        printf("p err\n");
    }
}
//v操作 释放资源
void sem_v(int index)
{
    if(index<0||index>=SEM_NUM)
    {
        return;
    }
    
    struct sembuf buf;
    buf.sem_num = index;
    buf.sem_op = 1//v;
    buf.sem_flg = SEM_UNDO;
    
    if(semop(semid,&buf,1) == -1)
    {
        printf("v err\n");
    	return;
    }
}
//销毁信号量
void sem_delete()
{
 if(semctl(semid ,0,IPC_RMID)==-1)//此处0只是占位,只要有删除,键中所有信号量都会删掉
 {
     return;
 }
}

共享内存

描述

​ 它允许两个不相关的进程访问同一个逻辑内存。

作用:为在多个进程间共享和传递数据提供了一种有效方式。

一般在实现时,不同进程间的共享内存安排为同一段物理内存。

​ 共享内存由ipc为进程创建的一个特殊的地址范围,它将出现在该进程的地址空间中。其他进程可以将同一段共享内存连接到它们自己的地址空间中。所有进程都可以访问共享内存中的地址。如果某个进程向内存写入了数据,所做的改动将立刻被可以访问同一段共享内存的任何其他进程看到。

​ 注意:在第一个进程结束对共享内存的写操作之前,并无自动的机制可以阻止第二个进程开始对它进行读取,对共享内存访问的同步控制必须由程序员来负责。

管道 ,是通过调用管道文件,来讲数据写到或者读取到进程中;而共享内存,是进程直接指向这一块儿内存区域中读取或写入数据,显然共享内存更加高效。

函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RqmhGzeq-1638457512199)(E:\linux jietu\屏幕截图 2021-11-21 202734.jpg)]

shmget()
int shmget(key_t key,size_t size,int shmflg)
//key 为共享内存命名 shmget函数返回一个共享内存标识符,此符号将用于后续的共享内存函数  IPC_PRIVATE用于创建一个只属于创建进程的共享内存。
//size 共享内存容量 以字节为单位
//shmflg  权限标志  IPC_CREAT和权限标志按位或才能创建一个新的共享内存段。 (有这个IPC_CREAT标志,传递的却是一个已有共享内存段的键并不是一个错误,该标志会被忽略)

权限标志允许一个进程创建的共享内存可以被共享内存的创建者所拥有的进程写入,同时其他用户创建的进程只能读取该共享内存。(就可以避免数据被其他用户修改)

成功返回标识符,失败返回-1。

shmat()

用于将该进程与共享内存连接

void*shmat(int shm_id,const void *shm_addr,int shmflg);
//shm_id 由shmget函数返回的共享内存标识符
//shm_addr 指定的是共享内存连接到该进程的地址位置。(通常是一个空指针,表示让系统来选择共享内存出现的地址)
//shmflg 可能由两个取值SHM_RND(与shm_addr联合使用,控制共享内存连接的地址)或者SHM_RDONLY(使得连接的内存只读),很少控制共享内存连接的地址,通常让系统来选择,以避免程序对硬件的依赖性过高。

调用成功返回一个指向共享内存第一个字节的指针,失败,就返回-1。

共享内存读写权限由它的属主(创建者),其访问权限和当前进程的属主决定。(但有一个例外,当SHMFLG&SHM_RDONLY为true时,此时即使共享内存访问权限允许写操作,也不可写)。

shmdt()
shmdt(void * addr)
    参数是shmat返回的内存地址。

成功返回0,失败返回-1

shmdt 用于将共享内存从当前进程中分离

注意 分离不是删除,只是此程序不再能使用该共享内存。

shmctl()

共享内存控制函数。

int shmctl(int shm_id,int command ,struct shmid_ds *buf);
struct shmid_ds{
    uid_t shm_perm.uid;
    uid_t shm_perm.gid;
    mode_t shm_perm.mode;
}
//shm_id 由shmget返回的共享内存标识符
//command 可取三个值 IPC_STAT 把shmid_ds结构中的数据设                              置为共享内存的当前关联值
		//		    IPC_SET  若进程有权限,把共享内存           //        的关联值设置为shmid_ds结构中给的值
	//				IPC_RMID  删除共享内存段
第三个参数 buf是一个指针,指向包含内存模式和访问权限的结构

成功返回0,失败返回-1。

注意:不要试图删除一个正处于连接状态的共享内存段

消息队列

描述:

​ 提供了一种在两个不相关的进程之间传递数据的相当简单且有效的方法

​ 与命名管道相比,消息队列独立于发送和接收进程而存在,这消除了在同步命名管道的打开和关闭时产生的困难。

​ 消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。而且,每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型值的数据块。

​ 我们可以通过发送消息来避免命名管道的同步和阻塞问题。还可以用一些方法提前查看紧急消息。但是:与管道一样,每个数据块都有一个最大长度的限制,系统中所有队列所包含的全部数据块的总长度也有上线。

​ LINUX由两个宏定义MSGMAX和MSGMNB,以字节为单位分别定义了一条消息的最大长度和一个队列的最大长度。其它系统的宏定义可能不一样甚至不存在。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HRLYmMLK-1638457512200)(E:\linux jietu\屏幕截图 2021-11-22 165019.jpg)]

函数:

msgget()
int msgget((key_t) key, int msgflg);
//key 此键值命名消息队列
//msgflg 权限标志 IPC_CREAT和权限标志位按位或才能创建一个新的消息队列,若已有,IPC_CREAT会被自动忽略

成功返回消息队列标识符,失败返回-1。

msgsnd()

用于把消息添加到消息队列中

int msgsnd(int msgid,const void *msg_ptr,size_t msg_sz,int msgflg);
//消息的结构受到两方面的约束。首先,它的长度必须小于系统规定的上限;其次必须以一个长整型成员变量开始,接收函数将用这个成员变量来确定消息的类型。最好把消息定义为:
struct my_message{
    long int message_type;
    /*你想要传的数据*/
}
第一个参数 msgid 是由msgget返回的消息队列标识符。
第二个参数msg_ptr 指向准备发送消息的指针,消息必须以一个长整型成员变量开始
第三个参数 msg_sz 是msg_ptr指向的消息的长度,不包括长整型消息类型成员变量的长度。
第四个参数msgflg 控制当前消息队列满或队列消息到达系统范围的限制时将要发生的事。
    	如果设置了IPC_NOWAIT标志,函数将立刻返回,不发送消息并且返回值为-1。若msgflg中IPC_NOWAIT标志被清楚,则发送进程将挂起以等待队列中腾出可用空间。

成功返回0,失败返回-1

msgrcv()

从消息对列中获取消息;

int msgrcv(int msgid,void *msg_ptr,size_t msg_sz, long int msgtype,int msgflg);
//第一个参数msgid 由msgget函数返回的队列标识符
//msg_ptr 指向准备接收消息的指针,要求同上
//msg_sz 是msg_ptr指向的消息长度,不包括长整型消息类型变量的长度
//msgtype 值为0 则获取队列中第一个可用消息,若大于0,获取具有相同消息类型的第一个消息,小于0 则获取消息类型等于或小于msgtype的绝对值的第一个消息
			一般按发送顺序接收,则设置为0,若获取特定类型消息,把msgtype设置为相应的类型值。
//msgflg用于控制队列中没有相应类型的消息可以接收时将发生的事。若设置的有IPC_NOWAIT,函数会立刻返回,返回值为-1,若IPC_NOWAIT被清楚,进程将会挂起等待一条相应类型的消息到达。

成功时返回放到接收缓存区中的字节数,消息被复制到msg_ptr指向的用户分配的缓存取中,然后删除消息队列中的对应消息,失败时返回-1.

msgctl()
int msgctl(int msgid,int command,struct msgid_ds *buf);
struct msgid_ds
{
	uid_t msg_perm.uid;
	uid_t msg_perm.gid;
	mode_t msg_perm.mode;
}
//msgid 由msgget返回的队列标识符
//command可取三个值
IPC_STAT  把消息队列的关联值给到此进程msgid_ds中。
IPC_SET  若进程有权限,把进程的msgid_ds给到消息对列关联值
IPC_RMID  删除消息队列

成功返回0,失败返回-1。

若删除消息队列,某个进程正在msgsnd或者msgrcv函数中等待,这两个函数失败。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值