我们常常使用的进程间通讯主要有信号、管道、共享内存、消息队列、信号量、socket这六种方式。
(一)信号
(1)它是一种通知机制;一种提前定义好的某些特定事件。信号可以被产生也可以被接收;信号最重要的就是如何发送信号,如何修改响应方式?Linux下支持的信号,举例有:
SIGCHLID:表示子进程终止,默认对此信号的响应方式是“SIG_IGN”忽略;
SIGINT:表示用户产生终止符,默认对此信号的响应方式是终止;
SIGKILL:表示不能被捕捉的终止进程的信号,默认对此信号的响应方式是终止;
SIGSTOP:不能被不捕获的挂起进程的信号,默认对此信号的响应方式是终止;
(2)发送信号
int kill(pid_t pid,int signo):给pid代表的进程发送信号;当pid==0时,信号被发送给调用进程的进程组的每个进程;pid==-1时,信号会向每个调用进程有权限发送信号的进程发出信号,调用进程自身和init除外;pid < -1时,信号被发送给进程组的-pid;
注意:给自己发送信号:int raise(int signal):调用成功后返回0,不成返回非0值,不设置error;
(3)修改信号的响应方式‘
1.常见的响应方式
SIG_DFL:默认 SIG_ING:忽略 自定义
2.修改响应方式的函数
typedef void (*Fun_Hangdle(int)
Fun_Handle Signal(int signum ,Fun_Handle fun(int)):完成 signum 参数执行的信号与fun函数绑定;当接收到 signum 这个信号,执行fun这个函数;
3.注意事项:signal仅仅修改信号发生时所需要调用函数(将信号的响应句柄修改成传递的函数指针),所以,此函数并不会阻塞。 修改信号的响应后,此进程就会一直以此方式处理信号,如果需要恢复到系统默认情况,必须重新修改。
4.举例说明:当进程第一次接收到SIGITNT信号后,打印“hello world”,第二次接受限号的时候结束进程;代码如下:
void fun(int sign)
{
printf("hello world\n");
signal(SIGINT,SIG_DFL);
}
int main()
{
signal(SIGINT,fun);
while(1)
{
sleep(1);
printf("bbbbbbbbbb\n");
}
}
总结说明信号就是一种通知机制,只是给一个提示信息,并没有真正的进行数据信息通讯,这是要注意的地方;
(二)管道
管道分为有名管道和无名管道,他们都是半双工通讯,但是也存在两者不同的地方;
(1)有名管道
首先有名管道是一种特殊的文件类型,在文件目录树中存在一个文件标识符,但是不占据磁盘空间,需要进行传递的信息缓存在内存中。
有名管道的创建:
mkfifo + 文件名
int mkfifo(const char *pathname,mode_t mode)pathname:文件路径名;mode:文件权限:O_RDONLY、O_WRONLY。
有名管道的访问还是以操作文件的方式访问,分为以下四种情况:
有名管道之所以可以在任意两个进程之间进行通讯的原因是在文件系统中存在文件标识,任何进程都可以操作管道文件,故而实现两个进程间的数据传递。
(2)无名管道
无名管道利用的是父子进程在fork之前共享已经打开的文件描述符。父子进程之间共享除栈和堆外的资源,包括.text/.data段等;
无名管道的限制就在于只能应用于父子进程之间。
创建无名管道: int pipe(int fds[2]) 0端读,1端写。
(三)共享内存
共享内存的实现原理是将物理内存上一块空间,直接映射到两个进程各自的虚拟地址空间上。每一个进程都可以直接通过虚拟地址访问物理内存,所以共享内存是最快的一种IPC。共享内存的物理空间对于访问他的进程而言,是一种临界资源。必须要做同步控制。与有名管道相比,少了copy过程,少了系统调用过程(操作管道文件需要操作文件函数open等的使用),节省时间。两者对比图如下:
共享内存操作函数集
(1)申请空间 int shmget(key_t key,size_t size,int flag)返回值:成功时返回共享内存id值;失败时为-1;
key_t:用户标识键,可以随便定义 ;size:申请多大的内存空间;flag:用户权权限以及IPC_CREAT;
注意申请空间操作系统以页划分。
(2)进程空间与内存空间链接 void * shmat(int id,void*addr,int flag ) 成功时返回指向共享存储的指针;失败时返回-1;
addr:连接到调用进程的地址;一般建议有内核自己去分配链接地址,此值设为NULL;
flag:为了让内核来分配链接地址的位置,所以设为0
(3)断开链接 int shmdt(void *addr)
(4)释放共享内存空间 int shmctl(int id,int cmd,struct shmid_s *buff/*返回的内核的属性信息*/)
(四)信号量
(1)原子操作:不可中断的操作,一旦操作开始执行,就不能停止,直到其运行结束;
(2)临界资源:在同一时刻只能被一个进程使用的资源;
(3)临界区:在程序中指访问临界资源的代码区域;
信号量是一种计数器,主要用来完成进程的同步控制;并且信号量是在内核实现的;信号量同样也没有具体实现数据的传递,只是资源的一种计数管理,此资源可以同时被多少个进程访问。信号量的操作都要依赖于内核对象(信号量集)
信号量操作的函数:
int semget((key_t)key,int num ,int flag) key:用户标识 Nums:指定信号量集中信号量的个数;flag:用户权限和IPC_CREAT
用来进行pv操作
int semop(int semid,struct sembuf*buff,int len) ,semid为信号量集的标识符,buff指针指向一个struct sembuff结构,如下图所示:
P操作:减一操作,减到不可减的时候,就会阻塞住;当资源被使用时,进行减一操作;
V操作:加一操作,当使用的资源被释放时,进行加一操作;
删除操作
Int semctl(int semid,int num,int cmd,/*union semun val;*/)
semid:信号量标识符;num:使用的某个信号量的下标;cmd:表示要对信号量执行什么命令;union semun val:是多个特定命令参数的联合,可以不用。
(五)消息队列
发送带有类型的数据,并且接受数据的一端在特定类型上按照先进先出的顺序获取数据。
操作消息对列的函数集
(1)创建或者获取已存在的消息队列
int msgget((key_t)key,int flag/*用户权限或者加创建*/)
(key_t)key:用户标识符;flag:用户权限(0666等设置)或者加创建;
假如在内存中本来就没有消息队列,我们可以通过IPC_CREAT进行创建;
(2)将数据写到消息队列中
int msgsnd(int misgid,const void *ptr,int len,int flag/*标志,无特殊需求为0*/)
其中ptr指针指向一个结构体,包含两部分内容:
struct mesg { long type ,char data[128]};
(3)从消息队列中取数据
ssize_t msgcrv(int msgid,void *ptr ,size_t nbyte, long type,int flag)返回成功时返回消息中数据的部分的长度,出错时返回-1
ptr:指向一个struct mseg结构体中的long type,后面跟的是这种数据类型的实际数据内容;
nbyte:消息中数据的长度大小;
type:可以指定我们想要消息队列中的那种类型的数据;如下图所示:
(4)删除消息队列
int msgctl(int msgid,int cmd,struct magid_ds *buf)
msgid:队列id; cmd:命令参数; buf:msgid_ds结构的buf,每个队列都有一个这样的结构;
(六)部分Linux命令
在内核中查询:
ipcs -q:查询消息队列
ipcs -s:查询信号量集
删除命令:
Ipcrm -q msgid:删除内核中的消息队列
Ipcrm -s semid:删除内核中的信号量
Ipcrm -m shmid:删除内核中的共享内存