进程间通信的方式
信号
信号的生成:一个进程发生了某些条件,系统产生一个事件,就是信号。
信号的发送:在终端中用命令发送信号,或者是在代码中用int kill (pid_t pid,int sig)函数发
送信号//把sig给定的信号发送给pid进程,失败时返回-1。
信号的捕获:接收到信号的进程会采取一些行动。1 默认方式SIG_DFL 2忽略 SIG_IGN 3自定义fun
信号的名称:在/usr/include/bits/signal.h中定义,常见的有:SIGINT终端中断
SIGCHLD子进程已经停止或退出(默认作忽略处理)
信号响应方式的设置:函数原型比较复杂,具体讲解可以看评论我贴出的别人的博客。
返回值是一个函数指针signal(信号值,文件指针类型的处理方式)
举例:signal(SIGINT,SIG_DFL);对按Ctrl+c终止程序产生的信号SIGINT作忽略处理。忽略第一次按下的Ctrl+c。
signal( SIGUSR1,func),对某进程发来的自定义信号SIGUSR1作调用func的处理。
管道
有名管道
创建管道文件命令:mkfifoname (有名管道)
管道文件中的数据在内存中存放
普通文件中的数据在磁盘中存放
管道这个文件,文件的创建的信息,存放在磁盘中,管道中的数据在内存中。
管道关闭之后,(关机之后,掉电之后),管道中的数据就丢失了。(因为数据在内存中存放)
管道文件必须两个进程分别以读和写同时打开 ,才能打开。
打开之后,读的进程如果读不到,会阻塞住。一直等,直到那边写入,这边读到,程序才往下走。
如果其中一个写的进程Ctrl c彻底关闭了,读的进程read返回一个0,并且不再阻塞。
打开管道文件后,在内存中分配了一块空间,并且有头指针和尾指针,当写入的时候头指针往后移,当读取时尾指针往后移,尾指针之前的数据再也读不到了。头指针指到这块空间的末尾时,就会从内存初始重新覆盖着写。用操作普通文件的open() read()write()close()系统调用就可以操作管道文件。
无名管道
通过系统调用函数pipe()创建无名管道,创建时可以同时拿到两个文件描述符,一个读一个写。
此时对通讯着的两个进程有要求,必须是通过fork创建的父子进程间才可以通讯。(因为没有名字,别的进程不知道如何打开,fork了,父进程可以将文件描述符传递给子进程)
#include <unistd.h>
int pipe(int file_descriptor[2]) //存放文件描述符的 整型数组
fd[0] 固定的读端
fd[1] 固定的写端
程序中实现:
写端彻底关闭,读端read返回0
读端彻底关闭,写端会引发异常,也会收到一个信号SIGPIPE,终止写端。
可以通过signal(),将信号的处理方式改变,不要终止写端。
总结:
有名管道和无名管道的区别?(有名管道可以在任意两个进程间使用
无名管道必须隶属于父子进程间使用)
有名管道和无名管道的相同点?(打开管道文件后,都是在对内存进行操作)
全双工与半双工与单工:管道(半双工)(一个进程在一个时间点只能读或者使只能写)
两个进程同时操作这个有名管道文件才可以打开
一个进程pipe一个无名管道文件可以先读后写
信号量
信号量是通过控制程序的推进速度来同步进程,为什么要同步进程呢,首先就要了解临界资源、临界区、死锁、竞争、原子操作的概念,在这里,我简单介绍一下,
临界资源:同一时刻,只允许一个(有限个)线程访问的资源。
临界区:访问临界资源的代码段。
原子操作:不可被分割(执行的时候不可被中断)的(一组)操作
信号量:特殊的变量,只能取正整数值,
作用: 信号量是控制线程对临界资源的访问,保证同一刻只有一个线程访问。当临界区域可用时,p操作将信号量-1,表示临界区正在被使用。如果信号量值=0,那么再进行-1操作时会阻塞。离开临界区时,用v操作对信号量+1,使临界区再次变为可用。并且+1v -1p是原子操作。
目的: 同步进程。
方法: 控制程序的推进速度。
使用方法:
semget():全新创建一个信号量,或者获取一个已有的信号量的值
Key:一个整型值,两个进程互相知道这个整型值。
Num_sems:信号量的个数,一个或者多个
Sem_flags:标志位,全新创建出一个信号量,失败返回-1
semop():对信号量的值进行修改 +1V -1P
semctl():对信号量做控制,为信号量赋初始值。初始值代表可用的资源数量。
初始化一个信号量:, 或者可以对信号量进行销毁
一个例子:
#include "sem.h"
static int semid=0;
void sem_init()
{
semid=semget((key_t)1234,1,IPC_CREAT|IPC_EXCL|0600); //全新创建一个信号量
if(semid == -1) //如果返回-1 ,代表已有这个信号量,或者出错
{
semid=semget((key_t)1234,1,IPC_CREAT|0600); /如果已有,获取它
if(semid==-1)//如果还是-1 代表出错
{
perror("semget error!\n");
}
}
Else //已全新创建出一个信号量
{
union semun a;
a.val=1;
if(semctl(semid,0,SETVAL,a)==-1) //为信号量附初值
perror("semctl error!\n");
}
}
void sem_p() // p操作 -1
{
struct sembuf buf; //初始化结构体
buf.sem_num=0; //改变第几个信号的值,,如果只有一个信号量,则是第0个
buf.sem_op=-1; //要改变的值。
buf.sem_flg=SEM_UNDO; //如果程序未释放,操作系统帮忙释放
if(semop(semid,&buf,1)==-1)
{
perror("p error");
}
}
void sem_v() // v操作 +1
{
struct sembuf buf;
buf.sem_num=0;
buf.sem_op=1; //要改变的值
buf.sem_flg=SEM_UNDO;
if(semop(semid,&buf,1)==-1)
{
perror("v error");
}
}
void sem_destory() //销毁的时候,不存在销毁信号量中哪一个,只会一次性销毁一组信号量 ,第二个参数是无效值。
{
union semun sem_union; //这行好像不是必要
if(semctl(semid,0,IPC_RMID,sem_union)==-1) //最后一个参数也不是必要
{
perror("semctl destory error!\n");
}
}
共享内存
共享内存与管道不同,管道必须两个进程同时使用,而且读进程读完的数据,指针再也获取不到。一次性。
共享内存只要存在,就可以让进程使用,并且其中的数据,在内存被移除之前都是存在的。使用起来更灵活。
使用方法:
- Shmget ()创建共享内存空间,或者获取一块已有的共享内存空间
- Shmat() 把共享内存映射到当前进程的虚拟地址空间中
第二个参数一般传空 NULL指针 意为让系统选择共享内存出现的地址
第三个参数 一般 也给空 0 表示一个标志位,可以不予设置
有两个可选项 第一个SHM_RND
第二个SHM_RDONLY 只读
- Shmdt() 断开映射
本进程不再能够使用,但是共享内存依然存在,别的进程还可以用。
其他进程通过相同的key值shmget()获得,
- Shmctl() 移除共享内存空间
消息队列
消息队列与命名管道有着许多相似的地方,消息队列的优点是它独立与发送和接收的进程而存在,命名管道必须两个进程一起操作,一方读另一方写。
Int msgget(key_t key ,int msgflg);//创建和访问一个消息队列。
参数: ( 用一个键值来命名某个特定的消息队列,第二个参数是IPC_CREAT)
返回值:代表队列标识符的正整数,失败时返回-1;
Int msgid=msgget((key_t)1234,0666|IPC|CREAT);
Int msgsnd(int msgid,const void*msg_ptr,size_t msg_sz,int msgflg);
第一个参数是msgget返回的消息队列标识符
第二个参数是指向准备发送消息的指针,指针类型是一个结构体,必须自己定义
Struct my_message{long int message_type;char buff[MAX];};//但必须以长整型变量开始。Struct my_message some_data;
第三个参数是要发送的消息的长度。
第四个参数是IPC_NOWAIT,如果消息队列满了,则函数立刻返回-1,不发送消息。
Msgsnd(msgid,(void *)&some_data,MAX,0);
Int msgrcv(int msgid,void *msg_ptr,size_tmsg_sz,long int msgtype,int msgflg);
第一个参数是消息队列标识符
第二个参数是指向准备接受消息的指针,也是一个结构体类型。
第三个参数是指针指向的消息的长度。
第四个参数一般取0,表示按照消息发送的顺序来接受他们。
第五个参数是
Msgrcv(msgid,(void *)&some_data,bufsize,msg_to_receive,0);
Int msgctl(int msgid,int command,structmsqid_ds *buf);
Struct msqid_ds{uid_t msg_perm.uid;
Uid_t msg_perm.gid;
Mode_t msg_perm.mode};
Msgctl(msgid,IPC_RMID,0);//删除消息队列。一方删除就可以了。
套接字
套接字既可以在本地进程间通信,也可以跨网络,在不同主机上的进程间通信。具体使用方法看我另外一篇转载的博客,写的很细。