700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > Linux系统编程-进程间通信

Linux系统编程-进程间通信

时间:2019-03-28 14:34:19

相关推荐

Linux系统编程-进程间通信

一.进程间通信概念

每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程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");}wsdxpid = fork();if(pid < 0){printf("creat child failed!\n");}else if(pid > 0){//父进程printf("this is father\n");close(fd[0]);write(fd[1],"this is father from",strlen("this is father from"));wait();}else{//子进程printf("this is child\n");close(fd[1]);read(fd[0],buf,128);printf("read from father:%s\n",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 failed\n");perror("why");}else{if(errno == EEXIST){printf("file have\n");}}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 = %x\n",key);//创建或打开消息队列,成功返回队列ID,失败返回-1int msgID = msgget(key,IPC_CREAT|0777);if(msgID == -1){printf("get msgID failed\n");}//读取消息,成功返回消息数据的长度,失败返回-1msgrcv(msgID,&readBuf,sizeof(readBuf.mtext),888,0);printf("read from que:%s\n",readBuf.mtext);struct msgbuf sndBuf = {988,"thank you for reach"};msgsnd(msgID,&sndBuf,strlen(sndBuf.mtext),0);//控制消息队列,成功返回0,失败返回-1msgctl(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 = %x\n",key);//创建或打开消息队列,成功返回队列ID,失败返回-1int msgID = msgget(key,IPC_CREAT|0777);if(msgID == -1){printf("get msgID failed\n");}//添加消息,成功返回0,失败返回-1msgsnd(msgID,&sndBuf,strlen(sndBuf.mtext),0);struct msgbuf readBuf;msgrcv(msgID,&readBuf,sizeof(readBuf.mtext),988,0);printf("read from que:%s\n",readBuf.mtext);//控制消息队列,成功返回0,失败返回-1msgctl(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,失败返回-1shmID = shmget(key,1024*4,IPC_CREAT|0600);if(shmID == -1){printf("shmget error!\n");exit(-1);}//连接共享内存到当前进程的地址空间,成功返回指向共享内存的指针,失败返回-1shmaddr = shmat(shmID,0,0);strcpy(shmaddr,"chenglicheng");sleep(5);//断开与共享内存的连接,成功返回0,失败返回-1shmdt(shmaddr);//控制共享内存的相关信息,成功返回0,失败返回-1shmctl(shmID,IPC_RMID,0);printf("quet\n");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,失败返回-1shmID = shmget(key,1024*4,0);if(shmID == -1){printf("shmget error!\n");exit(-1);}//连接共享内存到当前进程的地址空间,成功返回指向共享内存的指针,失败返回-1shmaddr = shmat(shmID,0,0);printf("data:%s\n",shmaddr);sleep(5);//断开与共享内存的连接,成功返回0,失败返回-1shmdt(shmaddr);printf("quet\n");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 5269int main(int argc,char **argv){int signum;int pid;char cnt[128];signum = atoi(argv[1]); //转换成整型数pid = atoi(argv[2]);printf("signum = %d,pid = %d\n",signum,pid);//kill(pid,signum);sprintf(cnt,"kill -%d %d",signum,pid);system(cnt);printf("send signal ok\n");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 = %d\n",signum);if(context != NULL){//判断是否有内容printf("get data = %d\n",info->si_int);//读取内容printf("get data = %d\n",info->si_value.sival_int);printf("from:5d\n",info->si_pid);//得到发送方的pid}}int main(){struct sigaction act;printf("pid = %d\n",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,down\n",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 key\n");}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 key\n");}int main(){int semID;key_t key;key = ftok(".",2);// 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1semID = semget(key,1,IPC_CREAT|0666);//信号量集合中中只有一个信号量union semun initsem;initsem.val = 0;//操作第0个信号量semctl(semID,0,SETVAL,initsem);//初始化信号量组//SETVAL设置信号量的值,设置为initsemint 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;}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。