一.进程间通讯(Inner-Process Communication)

概述:进程间通讯(IPC)是指在两个或多个不同的进程间传递或者交换信息。

1.进程间通讯:

不同进程之间进行数据或者信息的传递。

2.进程间目的:

  1. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/21767570/1624431200094-2030fd56-edab-4ea1-b06a-105621339018.png#align=left&display=inline&height=293&margin=%5Bobject%20Object%5D&name=image.png&originHeight=586&originWidth=879&size=322162&status=done&style=none&width=439.5)

2.1 数据传递

2.2 状态(事件)通知

2.3 资源同步和互斥

3.进程间通讯方式:

  1. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/21767570/1624431327327-3c38f3d1-387e-46ff-9fb2-a9b5ef8e6ba5.png#align=left&display=inline&height=239&margin=%5Bobject%20Object%5D&name=image.png&originHeight=477&originWidth=793&size=192883&status=done&style=none&width=396.5)

3.1 管道(无名管道/匿名管道piep,命名管道FIFO,标准流管道);

3.2 共享内存;

3.3 信号量;

3.4 信号(signal);

3.5 消息队列;

3.6 套接字(socket);

一、IPC-管道(内核空间内提供的管道)

image.png

4.1无名管道(匿名管道)

image.pngimage.png
Linux系统在内核空间创建的一个通讯通道,它具有读,写两端,进程可以通过无名管道进行数据读写;
特点: 1.单向 ,先进先出;
2.一旦管道中的数据被读走,数据就从管道移走了;
3.管道提供了简单的流控制机制,进程试图读取空管道,则进程阻塞,同理,进程想写入满管道,进程阻塞;

PIPE无名管道的创建:int pipe (int fd[2]);

应用场景:无名管道主要应用于具有亲缘关系(父子进程,兄弟进程)的进程之间;
优点:使用非常简单,一旦创建就可以直接获取读写两端的描述符,进行读写操作;
缺点:1.只能用于具有亲缘关系(父子进程,兄弟进程)的进程之间;
2.进程之间往往需要两个管道;
3.管道容量有限(64kb)
用法:
1.父进程创建管道;
2.父进程创建子进程;
3.父进程利用read,write 系统IO函数操作管道;
image.png
2021.6.24

4.2有名管道(FIFO)(命名管道)

image.png
就是一个特殊文件,在系统的文件结构中会产生相应的文件路径;之所以特殊,主要是因为管道文件中不存储数据,管道数据是存放在内存中;
有名管道(FIFO)特点:
1.单向,先进先出;
2.一旦管道中的数据被读走,数据就从管道移走了;
3.借助于命名管道进行通讯,必须既要有读进程,也要有写进程,否则进程会阻塞;
4.读进程对命名管道的操作会产生如下的情况:
1)管道为空,读进程阻塞
2)管道中的数据小于欲读取的数据,读进程读取到管道数据后,会返回;
3)管道中的数据大于欲读取的数据,读进程读取到欲读取数据后,返回;
5.写进程对命名管道的操作会产生如下情况:
1)管道满了,读进程阻塞;
2)管道中的空间小于欲写入的数据,写进程写入管道数据后,继续阻塞;
3)管道中的空间大于欲读取的数据,写进程写入管道数据,返回;
6.读写进程同时进行,中途有某些进程退出:会产生如下情况:
1)如果退出的是读进程,则写进程会受到SIGPIPE的信号通知,写进程可以执行自己接下来的操作,如果写进程不理会该信号,则写进程会终止写进程;
2)如果退出的是写进程 ,则读进程会 退出;
image.pngimage.png
用法:
1.每个进程先判断管道是否存在,若不存在创建,否则利用open函数打开命名管道;
2.各个进程利用read,write 系统IO函数操作管道;
3.进程利用close函数关闭管道;

命名管道创建int mkfifo(const char* pathname,mode_t mode);

应用场景:有名管道可以用于不相关的进程之间;
操作步骤:
1.open打开命名管道,获取管道描述符;
2.read,write进程管道读写;
3.close关闭管道描述符;
优点:可以使用于不相关的进程之间;
缺点:1.相比于无名管道,操作稍显复杂;
2.进程之间只使用命名管道通讯,往往需要两个管道;
3.管道的容量是有限的,(缺省 64KB);
4.命名管道容易造新堵塞;

image.png
标准流管道 popen

二、共享内存(systemV标准/ posix标准-跨平台)(资源共享)

image.png
共享内存是独立于进程地址空间之外的一块物理内存空间。共享内存可以被其他进程共同访问;
优点:1.效率高,IPC通讯方式中最快的一种;
2.容量大;

缺点:1.存在同步问题,需要程序员自己通过信号量 实现同步机制
用法:
1.每个进程创建或者获取共享内存 int shmid = shmget(key,size,IPC_CREAT|0664);
2.每个进程都要映射共享内存到进程地址空间 类型 p = (类型)shmat(shmid,NULL,0);
3.写进程通过 memset ,memcpy,memmove , *p = val 写入数据到共享内存;
读进程通过 memcpy, val** =*p 从共享内存中获取数据;
4.每个进程早操作完共享内存后,断开映射:shmdt(p);
5.由最后一个操作共享内存的进程回收共享内存:shmctl (shmid,IPC_RMID,NULL);**

操作步骤:image.pngimage.png
ipcs -m 查看共享内存
1.申请(创建)共享内存 shmget(key_t ) / shm_open() POSIX
2.共享内存映射 shmat() / attach
3.内存操作
4.共享内存解映射 shmdt() / deattach
5.回收共享内存 shmctl()
申请(创建)共享内存shmget()
image.png
共享内存映射 shmat()
image.png
共享内存解映射 shmdt()
image.png
2021.6.25
回收共享内存 shmctl()
image.png
struct shmid_ds
image.png

三、信号量(systemV)semaphore

1.信号量:信号量是一个内核变量,本质是一个计数器;利用信号量可以对共享资源(数据)进行受控访问;

信号量:计数器,计数值一旦<0 ,该信号量可以阻塞进程(线程);
用法:
1.每个进程创建或者获取 信号量集int semid = semget(key,num,IPC_CREAT|0664);
2.每个进程为信号量设初值**sem_init(int semid,int idex,int val)
3.各个进程在对共享内存访问前,要对信号量做p操作,要根据进程访问共享内存的顺**序合理选择需要做p操作的信号量;

4.各个进程在对共享内存访问后,需对另外的信号量做v操作;
5.由最后一个操作共享内存的进程回收信号量:**semctl(semid,2,PIC_RMID,NULL);**

2.信号量的操作:

image.png
1.p操作 给信号量减数(往往是信号量-1)
2.v操作 给信号量加数(往往是信号量+1)

3.信号量操作函数:

1.信号量创建semget()

eg : int semid = semget(ftok(“/home/ccw”,2),2,IPC_CREAT|0660);// 创建与获取
image.png

2.信号量操作(pv):int semop

  1. void sem_p(int semid,int idex)//p操作
  2. {
  3. struct sembuf sops = {idex,-1,SEM_UNDO};
  4. semop(semid,&sops,1);
  5. }
  6. void sem_v(int semid,int idex)//v操作
  7. {
  8. struct sembuf sops = {idex,1,SEM_UNDO};
  9. semop(semid,&sops,1);
  10. }

image.pngimage.png

3.信号量控制(pv) semctl

3.1 设置信号量的初始值
void sem_init(int semid,int idex,int val)
{
union semun
{
int val;
struct semid_ds buf;
unsigned short
array;
struct seminfo *_buf;
}un;
un.val = val;
semctl(semid,index,SETVAL,un);//一次设置一个信号量初值

/unsigned short sems[5]={1,0};
un.array = sems;
semctl(semid,2,SETALL,un);//借助数组一次设置多个信号量初值
/
}
联合体要自己定义
3.2回收信号量集合
semctl(semid,2,PIC_RMID,NULL);
image.pngimage.pngimage.png
同步问题的解决步骤:
1.创建多个信号量,其中一个信号量初值设为资源总数,另外初值设为0;
2.进程在共享资源访问前对信号量进行p操作,访问完成后对其他信号量做v操作;
3.如果设置某进程先访问资源,让该进程对初始值>0的信号量做p操作,其他进程对初始值为0的信号量做p操作;

四、消息队列

消息队列:由内核提供的消息的链表,该链表可以被不同的进程访问;
注意:放置到该队列中的消息(数据块)需要制定一个类型,获取消息的进程就是利用该类型来获取相关消息的;
优点:1.可应用于不相关的进程
2.消息队列不存在同步问题;
缺点:1.容量小
一条队列最大只能存储16KB的消息;
一条消息最大只能存储8KB的数据;
核心:消息结构体,结构体中2个成员(long 消息类型, char text[]: 消息的数据);
用法:
1.每个进程创建或获取消息队列 int msgid= msgget(key,IPC_CREAT|0664);
2.各个进程根据对消息队列的操作方式,采用msgsnd,msgrcv来发送消息;
3.
使用步骤:
1.申请/获取 消息队列: msgget
image.png
2.向队列中发送消息 msgsnd
image.png
3.从队列中获取消息 msgrcv
image.png
4.回收 msgctl
image.png
image.pngimage.png
image.png

五、信号(事件通知)

信号:信号是用软件模拟硬件中断的处理过程。因此信号也被称为软中断;
image.png
用法:
1.发送信号:
1.1可能借助其他进程间通讯方式获取对方的PID;
2.2在需要的时候借助kill函数发送信号;
2.信号的处理:
一般,如果需要处理一个信号,可以在进程开始处利用signal()/sigaction在系统中为某个信号,注册一个信号处理函数;

1.信号的处理方式:

1.忽略;
2.默认;
3.捕捉;(给信号指定处理函数)
4.屏蔽;(暂时阻塞)

image.pngimage.png
image.png image.png
image.png

2.相关操作函数:

1.signal(int signum,handler_t hadnler);
image.png
2.信号发送:
2.1进程给自身发送信号;
raise(int signum);
image.png
2.2进程给另一个进程发送信号:
kill(pid_t pid, int signum);
image.png
3.信号的屏蔽
sigprocmask(int how,const sigset_t set,sigset_t oldset);

image.png
信号集操作函数
sigemptyset(sigset_t set);
sigaddset(sigset_t
set,int signum);
sigdelset(sigset_t set,int signum);
sigefillset(sigset_t
set);
sigismember(const sigset_t *set,int signum);
image.png
4.信号发送(携带数据)
sigqueue(pid_t pid,int signum,const union sigval value);

  1. union sigval
  2. {
  3. int sival_int;
  4. void* sival_ptr;
  5. };
  6. //例子:进程给自身发送sigusr1信号,同时携带一个整形数据6757
  7. union sigval val;
  8. val.sival_int = 6757;
  9. sigqueue(getpid(),SIGUSR1,val);

进程给自己发信号;int raise();
image.png
5.处理信号的同时获取数据
int sigaction(int signum, struct sigaction act ,struct sigaction oldact);
struct sigaction
{
void (*sa_handler)(int);
};
主要关注成员:
1.sa_flags : 该成员用于对指定对信号设置何种格式的处理函数;sa_handler or sa_sigaction
如果取值为 SA_SIGINFO,则选择sa_sigaction格式的函数作为信号的 处理函数;
2.sa_handler: 指定信号的一般处理函数格式 用户需要指定具有类似于 void handler(int signum)的信号处理函数;
3.sa_sigaction:指定信号的扩展处理函数格式 用户需要提供具有类似于void handler(int signum)的信号处理函数;
获取信号携带的数据:必须为信号指定扩展处理函数;可以通过扩展处理函数第二个参数(siginfo_t类型)来获取信号携带的数据;
具体操作:根据siginfo_t结构体类型中的 si_int 或者 si_ptr 成员来获取;
例子:获取信号SIGUSR1 携带的整形数据;
struct sigaction act = {0};
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = handler();
sigaction(SIGUSR1,&acl,NULL);

void handler(int signum,siginfo_t info,void unused)
{
int value = info ->si_int;
}
image.png

image.png

六、定时器

定时器是一个系统资源,主要用于定时功能,
1.alarm (unsigned int seconds);//为进程安装一个以秒为单位的单次计时的定时器

image.png
2.ualarm(_useconde_t usec, _useconde_t interval);
为进程安装一个以微秒为单位的重复计时的定时器;
但请注意 : _useconde_t 类型的取值范围为0,1000000
image.png
注意:当定时器定时事件到达,内核会发送SIGALRM 信号给到调用进程,调用进程需要捕捉处理,否则SIGALRM默认会将调用进程终止的;

pause();
阻塞调用进程,直到某个信号的到来;