概述
一.进程间通信概念
每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication)
二.进程间通信的目的
数据传输:一个进程需要将它的数据发送给另一个进程
资源共享:多个进程之间共享同样的资源
通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)
进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常
三.进程间通信的六种方式
单机
半双工管道——PIPE
全双工管道——命名全双工管道
消息队列
信号量
共享内存
多机
套接字
STREAMS
1.半双工管道
创建管道:pipi函数
#include <unistd.h>
int pipe(int pipefd[2]);
pipe函数定义中的fd参数是⼀个⼤⼩为2的⼀个数组类型的指针。该函数成功时返回0,并将⼀对打开的⽂件描述符值填⼊fd参数指向的数组。失败时返回 -1并设置errno。
通过pipe函数创建的这两个⽂件描述符 fd[0] 和 fd[1] 分别构成管道的两端,fd[0]表示读端,fd[1]表示写端。
下面展示无名管道代码
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main()
{
int fd[2]; //两个文件描述符
int pid;
char buf[128];
if(pipe(fd) == -1){
printf("creat pipe failed!n");
}
wsdx pid = fork();
if(pid < 0){
printf("creat child failed!n");
}else if(pid > 0){ //父进程
printf("this is fathern");
close(fd[0]);
write(fd[1],"this is father from",strlen("this is father from"));
wait();
}else{ //子进程
printf("this is childn");
close(fd[1]);
read(fd[0],buf,128);
printf("read from father:%sn",buf);
exit(0);
}
return 0;
}
2.有名管道
函数原型如下:
#include <sys/stat.h>
#include <sys/types.h>
int mkfifo( const char * filename, mode_t mode );
mkfifo函数中参数mode指定FIFO的读写权限,新创建FIFO的用户ID和组ID规则域open函数相同。参数filename指定新创建FIFO的文件名称。函数如果成功返回0,出 错返回–1,并更改errno的值。errno有可能出现的值为:EACCESS、EEXIST、ENAMETOO- LONG、ENOENT、ENOSPE、ENOTDIR和EROFS。
mkfifo 功能
mkfifo() 函数创建一个名为 pathname 的 FIFO 特殊文件,mode 参数用于指定权限。创建的 FIFO 特殊文件与管道是类似的,都可以用于进程间通信。这种特殊的FIFO文件可以被文件系统加载,因此可以像普通文件一样读写和删除。
使用mkfifo函数创建了FIFO特殊文件后,任何进程都可以像普通文件一样打开之,并读写。通常,读取FIFO特殊文件会被阻塞,直到有进程写入数据到FIFO文件。
mkfifo函数的简单demo
// ------read------
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
int main()
{
int ret = mkfifo("./file",0600);
char buf[30] = {0};
if(ret == -1 && errno != EEXIST){
printf("mkfifo failedn");
perror("why");
}else{
if(errno == EEXIST){
printf("file haven");
}
}
int fd = open("./file",O_RDONLY);
int n_read = read(fd,buf,30);
printf("read %d byte from fifo,context: %s n",n_read,buf);
close(fd);
return 0;
}
// ------write------
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
int main()
{
int cnt = 0;
char *str = "message from fifo";
int fd = open("./file",O_WRONLY);
while(1){
int n_write = write(fd,str,strlen(str));
cnt++;
sleep(1);
if(cnt == 5){
exit(0);
}
}
return 0;
}
3.消息队列
基本概念
消息队列就是⼀个存储消息的链表,这些消息具有特定的格式,以及特定的优先级。
对消息队列有写权限的进程可以向其中添加新消息,对消息队列有读权限的进程则可以从其中读取消息。 消息队列是随内核持续的,只有在内核重启,或者删除⼀个消息队列时,该消息队列才会真正地被删除。
用户消息缓冲区
无论发送进程还是接收进程,都需要在进程空间中用消息缓冲区来暂存消息。该消息缓冲区的结构定义如下
struct msgbuf {
long mtype; /* 消息的类型 /
char mtext[1]; / 消息正文 */
};
可通过mtype区分数据类型,同过判断mtype,是否为需要接收的数据
mtext[]为存放消息正文的数组,可以根据消息的大小定义该数组的长度
------创建消息队列------
通过msgget创建消息队列
函数原型如下
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
参数:
key: 某个消息队列的名字
msgflg:由九个权限标志构成,用法和创建文件时使用的mode模式标志是一样的,这里举两个来说明
IPC_CREAT
如果消息队列对象不存在,则创建之,否则则进行打开操作
IPC_EXCL
如果消息对象不存在则创建之,否则产生一个错误并返回
返回值:
成功msgget将返回一个非负整数,即该消息队列的标识码;
失败则返回“-1”
那么如何获取key值?
通过宏定义key值
通过ftok函数生成key值,这里就不具体介绍ftok函数用法
------添加信息到消息队列------
向消息队列中添加数据,使用到的是msgsnd()函数
函数原型如下
int msgsnd(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);
参数:
msgid: 由msgget函数返回的消息队列标识码
msg_ptr:是一个指针,指针指向准备发送的消息,
msg_sz:是msg_ptr指向的消息长度,消息缓冲区结构体中mtext的大小,不包括数据的类型
msgflg:控制着当前消息队列满或到达系统上限时将要发生的事情
如:
msgflg = IPC_NOWAIT 表示队列满不等待,返回EAGAIN错误
返回值:
成功返回0
失败则返回-1
------从消息队列中读取消息------
从消息队列中读取消息,我们使用msgrcv()函数,
函数原型如下
int msgrcv(int msgid, void *msg_ptr, size_t msgsz,
long int msgtype, int msgflg);
参数:
msgid: 由msgget函数返回的消息队列标识码
msg_ptr:是一个指针,指针指向准备接收的消息,
msgsz:是msg_ptr指向的消息长度,消息缓冲区结构体中mtext的大小,不包括数据的类型
msgtype:它可以实现接收优先级的简单形式
msgtype=0返回队列第一条信息
msgtype>0返回队列第一条类型等于msgtype的消息
msgtype<0返回队列第一条类型小于等于msgtype绝对值的消息
msgflg:控制着队列中没有相应类型的消息可供接收时将要发生的事
msgflg=IPC_NOWAIT,队列没有可读消息不等待,返回ENOMSG错误。
msgflg=MSG_NOERROR,消息大小超过msgsz时被截断
注意
msgtype>0且msgflg=MSC_EXCEPT,接收类型不等于msgtype的第一条消息
返回值:
成功返回实际放到接收缓冲区里去的字符个数
失败,则返回-1
------消息队列的控制函数------
函数原型
int msgctl(int msqid, int command, strcut msqid_ds *buf);
参数:
msqid: 由msgget函数返回的消息队列标识码
command:是将要采取的动作,(有三个可取值)分别如下
注意:若选择删除队列,第三个参数传NULL
返回值:
如果操作成功,返回“0”;如果失败,则返回“-1”
查看消息队列
查看消息队列
ipcs -q 命令查看已经创建的消息队列,包括他的key值信息,id信息,拥有者信息,文件权限信息,已使用的字节数,和消息条数。
ipcrm -Q加消息队列的key值,或来删除一个消息队列。
下面展示一些demo
// —————-msgget—————-
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
// int msgget(key_t key, int msgflg);
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
// ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
// int msgctl(int msqid, int cmd, struct msqid_ds *buf);
struct msgbuf {
long mtype; /* 消息的类型 */
char mtext[128]; /* 消息正文 */
};
int main()
{
struct msgbuf readBuf;
key_t key;
key = ftok(".",'z');
printf("key = %xn",key);
//创建或打开消息队列,成功返回队列ID,失败返回-1
int msgID = msgget(key,IPC_CREAT|0777);
if(msgID == -1){
printf("get msgID failedn");
}
//读取消息,成功返回消息数据的长度,失败返回-1
msgrcv(msgID,&readBuf,sizeof(readBuf.mtext),888,0);
printf("read from que:%sn",readBuf.mtext);
struct msgbuf sndBuf = {988,"thank you for reach"};
msgsnd(msgID,&sndBuf,strlen(sndBuf.mtext),0);
//控制消息队列,成功返回0,失败返回-1
msgctl(msgID,IPC_RMID,NULL); //删除消息队列
return 0;
}
// ——————msgsnd
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
// int msgget(key_t key, int msgflg);
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
// ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
struct msgbuf {
long mtype; /* 消息的类型 */
char mtext[128]; /* 消息正文 */
};
int main()
{
struct msgbuf sndBuf = {888,"this is message from quen"};
key_t key;
key = ftok(".",'z');
printf("key = %xn",key);
//创建或打开消息队列,成功返回队列ID,失败返回-1
int msgID = msgget(key,IPC_CREAT|0777);
if(msgID == -1){
printf("get msgID failedn");
}
//添加消息,成功返回0,失败返回-1
msgsnd(msgID,&sndBuf,strlen(sndBuf.mtext),0);
struct msgbuf readBuf;
msgrcv(msgID,&readBuf,sizeof(readBuf.mtext),988,0);
printf("read from que:%sn",readBuf.mtext);
//控制消息队列,成功返回0,失败返回-1
msgctl(msgID,IPC_RMID,NULL); //删除消息队列
return 0;
}
4.共享内存
共享内存IPC原理:
共享内存进程间通信机制主要用于实现进程间大量的数据传输,下图所示为进程间使用共享内存实现大量数据传输的示意图:
共享内存是在内存中单独开辟的一段内存空间,这段内存空间有自己特有的数据结构,包括访问权限、大小和最近访问的时间等。
两个进程在使用此共享内存空间之前,需要在进程地址空间与共享内存空间之间建立联系,即将共享内存空间挂载到进程中。
1.创建共享内存
#include <sys/ipc.h> #include <sys/shm.h>
/*
- 第一个参数为 key 值,一般由 ftok() 函数产生
- 第二个参数为欲创建的共享内存段大小(单位为字节)
- 第三个参数用来标识共享内存段的创建标识
*/
int shmget(key_t key, size_t size, int shmflg);
2.共享内存控制
#include <sys/ipc.h> #include <sys/shm.h>
/*
- 第一个参数为要操作的共享内存标识符
- 第二个参数为要执行的操作
- 第三个参数为 shmid_ds 结构的临时共享内存变量信息
*/
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
3.映射共享内存对象
系统调用 shmat() 函数实现将一个共享内存段映射到调用进程的数据段中,并返回内存空间首地址,其函数声明如下:
#include <sys/types.h>
#include <sys/shm.h>
/*
- 第一个参数为要操作的共享内存标识符
- 第二个参数用来指定共享内存的映射地址,非0则为此参数,为0的话由系统分配
- 第三个参数用来指定共享内存段的访问权限和映射条件
*/
void *shmat(int shmid, const void *shmaddr, int shmflg);
4.分离共享内存对象
在使用完毕共享内存空间后,需要使用 shmdt() 函数调用将其与当前进程分离。函数声明如下:
#include <sys/types.h>
#include <sys/shm.h>
/*
- 参数为分配的共享内存首地址
*/
int shmdt(const void *shmaddr);
共享内存的demo
// 共享内存_写
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
// int shmget(key_t key, size_t size, int shmflg);
int main()
{
int shmID;
char *shmaddr;
key_t key;
key = ftok(".",26);
//创建或获取一个共享内存,成功返回共享内存ID,失败返回-1
shmID = shmget(key,1024*4,IPC_CREAT|0600);
if(shmID == -1){
printf("shmget error!n");
exit(-1);
}
//连接共享内存到当前进程的地址空间,成功返回指向共享内存的指针,失败返回-1
shmaddr = shmat(shmID,0,0);
strcpy(shmaddr,"chenglicheng");
sleep(5);
//断开与共享内存的连接,成功返回0,失败返回-1
shmdt(shmaddr);
//控制共享内存的相关信息,成功返回0,失败返回-1
shmctl(shmID,IPC_RMID,0);
printf("quetn");
return 0;
}
// 共享内存_读
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
// int shmget(key_t key, size_t size, int shmflg);
int main()
{
int shmID;
char *shmaddr;
key_t key;
key = ftok(".",26);
//创建或获取一个共享内存,成功返回共享内存ID,失败返回-1
shmID = shmget(key,1024*4,0);
if(shmID == -1){
printf("shmget error!n");
exit(-1);
}
//连接共享内存到当前进程的地址空间,成功返回指向共享内存的指针,失败返回-1
shmaddr = shmat(shmID,0,0);
printf("data:%sn",shmaddr);
sleep(5);
//断开与共享内存的连接,成功返回0,失败返回-1
shmdt(shmaddr);
printf("quetn");
return 0;
}
5.信号
1.基本概念
信号是事件发生时对进程的通知机制,也就是所谓的软件中断。信号和硬件的中断类似,是软件层对中断机制的模拟,在多数情况下是无法预测信号产生的时间,所以软件层提供了一种处理异步事件的方法。
2.信号的来源分为硬件来源和软件来源。
硬件来源:
硬件发生异常,即硬件检测到错误条件并通知内核,随即再由内核发送相应的信号给相关进程,如除数为0、无效的内存引用等。
用户按终端键,引起终端产生的信号(比如Ctrl + C键产生SIGINT)。
软件来源:
用户通过指令杀死,如kill指令。
发生软件事件, 如程序执行raise, alarm、setitimer、sigqueue等函数。
3.信号处理
信号通常是发送给对应的进程,当信号到达后,该进程需要做出相应的处理措施,通常进程会视具体信号执行相应的操作,有三种操作方式。
忽略信号:
信号到达后、直接忽略,就好像是没有出该信号,信号对该进程不会产生任何影响。事实上,大多数信号都可以使用这种方式进行处理,但有两种信号却决不能被忽略,分别是SIGKILL 和 SIGSTOP。
捕获信号:
当信号到达进程后,执行signal()绑定好的信号处理函数。
执行系统默认操作:
进程不对该信号事件作出处理,而是交由系统进行处理,每一种信号都会有其对应的系统默认的处理方式。
4.常见的信号
在linux系统中通过kill -l命令可以查看到相应的信号。信号编号是从 1 开始,不存在编号为 0 的信号,事实上 kill()函数对信号编号 0 有着特殊的应用。
注意:括号" ) "前面的数字对应该信号的编号,编号 1~31 所对应的是不可靠信号,编号 34~64 对应的是可靠信号,从图中可知,可靠信号并没有一个具体对应的名字,而是使用了 SIGRTMIN+N 或 SIGRTMAXN 的方式来表示。其中32和33空缺。
信号处理函数的注册
信号处理函数的注册不只一种方法,分为入门版和高级版
1.入门版:函数 signal
2.高级版:函数sigaction
信号处理发送函数
信号发送函数也不止一个,同样分为入门版和高级版
1.入门版:kill
2.高级版:sigqueue
————signal()
"signal.h"信号处理库提供了signal函数,用来捕获突发事件。以下是 signal() 函数的语法ads。
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_thandler);
signum:可使用信号名(宏)或信号的数字编号,建议使用信号名。
handler:参数 handler 既可以设置为用户自定义的函数,也可以设置为 SIG_IGN 或 SIG_DFL,SIG_IGN 表示此进程需要忽略该信号,SIG_DFL 则表示设置为系统默认操作。
—————sigaction()
除了signal()之外,sigaction()系统调用是设置信号处理方式的另一选择,虽然 signal()函数简单好用,而 sigaction()更为复杂,但作为回报,sigaction()也更具灵活性以及移植性。
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
signum:需要设置的信号,除了 SIGKILL 信号和 SIGSTOP 信号之外的任何信号。
act:参数 act 不为 NULL,则表示需要为信号设置新的处理方式;如果参数 act 为 NULL,则表示无需改变信号当前的处理方式
oldact:参数oldact 不为 NULL,则会将信号之前的处理方式等信息通过参数 oldact 返回出来;如果无意获取此类信息,那么可将该参数设置为 NULL。
返回值:成功返回 0;失败将返回-1,并设置 errno。
struct sigaction 结构体
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
sa_handler:指定信号处理函数,与 signal()函数的 handler 参数相同。
sa_sigaction:也用于指定信号处理函数,这是一个替代的信号处理函数。
sa_mask:参数 sa_mask 定义了一组信号。
sa_restorer:该成员已过时,不要再使用了。
sa_flags:参数 sa_flags 指定了一组标志,这些标志用于控制信号的处理过程。
—————kill()
kill()系统调用可将信号发送给指定的进程或进程组中的每一个进程。
int kill(pid_t pid, int sig);
pid:参数 pid 为正数的情况下,用于指定接收此信号的进程 pid。
sig:参数 sig 指定需要发送的信号,也可设置为 0,如果参数 sig 设置为 0 则表示不发送信号,但任执行错误检查,这通常可用于检查参数 pid 指定的进程是否存在。
返回值:成功返回 0;失败将返回-1,并设置 errno。
keil()的demo
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
// int kill(pid_t pid, int sig);
// ./a.out 9 5269
int main(int argc,char **argv)
{
int signum;
int pid;
char cnt[128];
signum = atoi(argv[1]); //转换成整型数
pid = atoi(argv[2]);
printf("signum = %d,pid = %dn",signum,pid);
// kill(pid,signum);
sprintf(cnt,"kill -%d %d",signum,pid);
system(cnt);
printf("send signal okn");
return 0;
————-sigqueue()
#include<signal.h>
int sigqueue(pid_t pid,int sig,const union sigval value);
pid是目标进程的进程号
sig是信号代号
value参数是一个联合体,表示信号附带的数据,附带数据可以是一个整数也可以是一个指针,有如下形式:
union sigval {
int sival_int;
void *sival_ptr;//指向要传递的信号参数
};value
// ------接受消息------
#include <stdio.h>
#include <signal.h>
// sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
void handle(int signum,siginfo_t *info,void *context)
{
printf("get signum = %dn",signum);
if(context != NULL){ //判断是否有内容
printf("get data = %dn",info->si_int); //读取内容
printf("get data = %dn",info->si_value.sival_int);
printf("from:5dn",info->si_pid); //得到发送方的pid
}
}
int main()
{
struct sigaction act;
printf("pid = %dn",getpid());
act.sa_sigaction = handle;
act.sa_flags = SA_SIGINFO; //be able to get message
sigaction(SIGUSR1,&act,NULL); //注册信号
while(1);
return 0;
}
// ------发送消息------
#include <stdio.h>
#include <signal.h>
// int sigqueue(pid_t pid, int sig, const union sigval value);
int main(int argc,char **argv)
{
int signum;
int pid;
signum = atoi(argv[1]);
pid = atoi(argv[2]);
union sigval value;
value.sival_int = 100;
sigqueue(pid,signum,value);
printf("%d,downn",getpid());
return 0;
}
6.信号量
信号量(semaphore)与已经介绍过的IPC结构不同,他是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间数据通信。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
// 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
int semget(key_t key, int num_sems, int sem_flags);
// 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
int semop(int semid, struct sembuf semoparray[], size_t numops);
// 控制信号量的相关信息
int semctl(int semid, int sem_num, int cmd, …);
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdlib.h>
// int semget(key_t key, int num_sems, int sem_flags);
// int semctl(int semid, int sem_num, int cmd, ...);
// int semop(int semid, struct sembuf semoparray[], size_t numops);
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
void pGetKey(int id)
{
struct sembuf sops;
sops.sem_num = 0;
sops.sem_op = -1; //钥匙减一
sops.sem_flg = SEM_UNDO;
semop(id,&sops,1);
printf("get keyn");
}
void vPutBackKey(int id)
{
struct sembuf sops;
sops.sem_num = 0;
sops.sem_op = 1; //钥匙减一
sops.sem_flg = SEM_UNDO;
semop(id,&sops,1);
printf("put back keyn");
}
int main()
{
int semID;
key_t key;
key = ftok(".",2);
// 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
semID = semget(key,1,IPC_CREAT|0666);
//信号量集合中中只有一个信号量
union semun initsem;
initsem.val = 0;
//操作第0个信号量
semctl(semID,0,SETVAL,initsem); //初始化信号量组
//SETVAL设置信号量的值,设置为initsem
int pid = fork();
if(pid > 0){
pGetKey(semID);
printf("this is father!n");
vPutBackKey(semID);
}else if(pid == 0){
printf("this is child!n");
vPutBackKey(semID);
}else{
printf("fork error");
exit(-1);
}
return 0;
}
最后
以上就是粗心大象为你收集整理的Linux系统编程-进程间通信一.进程间通信概念二.进程间通信的目的三.进程间通信的六种方式的全部内容,希望文章能够帮你解决Linux系统编程-进程间通信一.进程间通信概念二.进程间通信的目的三.进程间通信的六种方式所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复