进程间通讯

来源:互联网 发布:isignature签章软件 编辑:程序博客网 时间:2024/06/07 06:03

一.进程间通讯概述

为什么进程间需要通信:

1.数据传输

一个进程需要将它的数据发送给另一个进程。

2.资源共享

多个进程之间共享同样的资源。

3.通知事件

    一个进程需要向另一个或一组进程发送消息,通知它们发生了某种事件。

4.进程控制。

有些进程希望完全控制另一个进程的执行(如DEBUG进程),此时控制进程希望能够拦截另一个进程的所有操作,并能够及时知道它的状态改变。LINUX进程间通信(IPC)由以下几部分发展而来:1.UNIX进程间通信2.基于System V进程间通信3.POSIX进程间通信现在Linux使用的进程间通信方式包括:1.管道(pipe)和有名管道(FIFO)2.信号(signal)3.消息队列4.共享内存5.信号量6.套接字(socket)

二,管道通信

1.什么是管道:

管道是单向的,先进先出的,它把一个进程的输出和另一个进程的输入连接在一起。一个进程(写进程)在管道的尾部写入数据,另一个进程(读进程)从管道的头部读出数据。
数据被一个进程读出后,将被从管道中删除,其他进程将不能读到这些数据。管道提供了简单的流控制机制,进程试图读空管道时,进程将阻塞。同样,管道已经满时,进程再试图向管道写入数据,进程将阻塞。

http://img.blog.csdn.net/20160802161044119

2.管道创建

管道包括无名管道和有名管道两种,前者用于父进程和子进程间的通信,后者可用于运行于同一系统中的任意两个进程间的通信。
无名管道有pipe()函数创建:

int pipe(int filedis[2]);

当一个管道建立时,它会创建两个文件描述符:**filedis[0]用于读管道,filedis[1]用于写管道。

3.管道关闭

关闭管道只需要将这两个文件描述符关闭即可,可以使用普通的close函数逐个关闭。
#include<unistd.h>#include<errno.h>#include<stdio.h>#include<stdlib.h>int main(){    int pipe_fd[2];    if(pipe(pipe_fd)<0)    {        printf("pipe create errno\n");        return -1;    }    else        printf("pipe create success\n");    close(pipe_fd[0]);    close(pipe_fd[1]);}

4.管道读写

管道用于不同进程间通信。通常先创建一个管道,再通过fork函数创建一个子进程,该子进程会继承父进程所创建的管道。
必须在系统调用fork()前调用pipe(),否则子进程将不会继承文件描述符
这里写图片描述
5.命名管道

命名管道和无名管道基本相同,但也有不同点:无名管道只能由父子进程使用;但是通过命名管道,不相关的进程也能交换数据。

//创建#include<sys/types.h>#include<sys/stat.h>int mkfifo(const char *pathname,mode_t mode)//一旦创建了一个FIFO,就可用open打开它,一般的文件访问函数(close,read,write)都可用于FIFO.**实际上管道类似于文件**

三、信号通讯

1.信号机制是Unix系统中最为古老的进程间通信机制,很多条件都可
以产生一个信号:
(1)当用户按某些按键时,产生信号。
(2)硬件异常产生信号。
(3)进程用kill函数将信号发送给另一个进程
(4)用户可用kill命令将信号发送给其他进程

2.信号类型

常见的几种信号:
SIGHUP:从终端上发出的结束信号
SIGINT:来自键盘的中断信号(Ctrl+c)
SIGKILL:该信号结束接收信号的进程
SIGTERM:kill命令发出的信号
SIGCHLD:标识子进程停止或结束的信号
SIGSTOP:来自键盘(ctrl+z)或调试程序的停止执行信号

3.信号处理

当某信号出现时,将按照下列三种方式中的一种进行处理:

(1)忽略此信号
大多数信号都按照这种方式进行处理,但有两种信号却不能被忽略。他们是:SIGKILL和SIGSTOP。这两种信号不能被忽略的原因是:他们向超级用户提供了一种终止或停止进程的方法。
(2)执行用户希望的动作
通知内核在某种信号发生时,调用一个用户函数。在用户函数中,执行用户希望的处理。
(3)执行系统默认动作
对大多数信号的系统默认动作是终止该进程。

4.信号发送

发送信号的主要函数有kill和raise.

区别:
kill既可以向自身发送信号,也可以向其他进程发送信号。与kill函数不同的是,raise函数是向进程自身发送信号。

#include<sys/types.h>#include<signal.h>int kill(pid_t pid,int signo)int raise(int signo)

Alarm:
使用alarm函数可以设置一个时间值(闹钟时间),当设置的时间到了时,产生SIGALRM信号。如果不捕捉此信号,则默认动作是终止该进程。

#include<unistd.h>unsigned int alarm(unsigned int seconds)//seconds:经过了指定的seconds秒后会产生信号SIGALRM//每个进程只能有一个闹钟时间。如果在调用alarm时,以前已为该进程设置过闹钟时间,而且它还没有超时,以前登记的闹钟时间则被新值代换。//如果有以前登记的尚未超时的闹钟时间,而这次seconds值是0,则表示取消以前的闹钟。
//Pause函数使调用进程挂起直至捕捉到一个信号。wait则是等待一个结束。#include<unistd.h>int pause(void)

当系统扑捉到某个信号时,可以忽略该信号或是使用指定的处理函数来处理该信号,或者使用系统默认的方式。
信号处理的主要方法有两种,一种是使用简单的signal函数,另一种是使用信号集函数组。

#include<signal.h>void (*signal(int signo,void(*func)(int)))(int)typedef void (*sighandler_t)(int)    sighandler_t signal(int signum,sighandler_t hander)Func可能的值是:    1、SIG_IGN:忽略此信号    2、SIG_DFL:按系统默认方式处理    3、信号处理函数名:使用该函数处理

四、共享内存

1.概述

共享内存是被多个进程共享的一部分物理内存。共享内存是进程间共享数据的一种最快的方法,一个进程向共享内存区域写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容。
这里写图片描述

2.实现

共享内存实现分为两个步骤:(1)创建共享内存,使用shmget函数。(2)映射共享内存,将这段创建的共享内存映射到具体的进程空间去,使用shmat函数。
1)创建int shmget(key_t key,int size,int shmflg)//key标识共享内存的键值:0/IPC_PRIVATE.当key的取值为IPC_PRIVATE,则函数shmget()将创建一块新的共享内存;如果key//的取值为0,而参数shmflg中又设置IPC_PRIVATE这个标志,则同样会创建一块新的内存。//返回值:如果成功,返回共享内存标识符;如果失败,返回-1.2)映射int shmat(int shmid,char *shmaddr,int flag)//参数://  shmid:shmget函数返回的共享存储标识符//  flag:决定以什么方式来确定映射的地址(通常为0)//返回值://  如果成功,则返回共享内存映射到进程中的地址;如果失败,则返回-1
//当一个进程不再需要共享内存时,需要把它从进程地址空间脱离    int shmdt(char *shmaddr)
#include <stdlib.h>#include <stdio.h>#include <string.h>#include <errno.h>#include <unistd.h>#include <sys/stat.h>#include <sys/types.h>#include <sys/ipc.h>#include <sys/shm.h>#define PERM S_IRUSR|S_IWUSR/* 共享内存 */int main(int argc,char **argv) {     int shmid;     char *p_addr,*c_addr;     if(argc!=2)     {         fprintf(stderr,"Usage:%s\n\a",argv[0]);         exit(1);     }    /* 创建共享内存 */        if((shmid=shmget(IPC_PRIVATE,1024,PERM))==-1)     {         fprintf(stderr,"Create Share Memory Error:%s\n\a",strerror(errno));         exit(1);     }     /* 创建子进程 */    if(fork()) // 父进程写    {         p_addr=shmat(shmid,0,0);         memset(p_addr,'\0',1024);         strncpy(p_addr,argv[1],1024);        wait(NULL); // 释放资源,不关心终止状态        exit(0);     }     else       // 子进程读    {         sleep(1); // 暂停1秒,防止子进程先运行          c_addr=shmat(shmid,0,0);//第二个0表示系统选默认地址        printf("Client get %p\n",c_addr);         exit(0);     } } 

五.消息队列

1.概述

和管道相比消息队列的数据带有固定格式,而不是像管道里的数据没有任何格式,但是和管道类似数据一被读走就不再存在,而不像共享内存读了之后数据仍然存在。
消息队列就是一个消息的链表。可以把消息看作一个记录,具有特定的格式。进程可以向中按照一定的规则添加新消息;另一些进程则可以从消息队列中读走消息。
目前主要有两种类型的消息队列:POSIX消息队列以及系统V消息队列,后者目前被大量使用,该消息队列是随内核持续的,只有在内核重起或者人工删除时,该消息队列才会被删除。
消息队列的内核持续性要求每个消息队列都在系统范围内对应唯一的键值,所以,要获得一个消息队列的描述字,必须提供该消息队列的键值。

1)键值#include<sys/types.h>#include<sys/ipc.h>    key_t ftok9char *pathname,char proj)//功能:返回文件名对应的键值。proj:项目名(不为0即可)(2)打开/创建#include<sys/types.h>#include<sys/ipc.h>#include<sus/msg.h>    int msgget(key_t key,int msgflg)//返回值:与键值key相对应的消息队列描述字。//key:键值,由ftok获得。//msgflg:标志位。//IPC_CREAT创建新的消息队列//IPC_EXCL与IPC_CREAT一同使用,表示如果要创建的消息队列已经存在,则返回错误。//IPC_NOWAIT读写消息队列无法得到满足时,不阻塞。//在一下两种情况下,将创建一个新的消息队列:    如果没有与键值key相对应的消息队列,并且msgflg中包含了IPC_CREAT标志位。    key参数为IPC_PRIVATE    int open_queque(key_t keyval)    {        intqid;        if((qid=msgget(keyval,IPC_CREAT))==-1)        {            return(-1);        }        return(qid);    }(3)发送消息#include<sys/type.h>#include<sys/ipc.h>#include<sys/msg.h>int msgsnd(int msqid,struct msgbuf *msgp,int msgsz,int msgflg)//功能:向消息队列中发送一条消息。//msgqid已打开的消息队列id//msgp存放消息的结构//msgsz消息数据长度//msgflg发送标志,有意义的msgflg标志为IPC_NOWAIT,指明在消息队列没有足够空间容纳要发送的消息时,msgsnd是否等待。struct  msgbuf{    long mtype;//消息类型>0    char mtext[1];//消息数据的首地址};(4)接收消息#include<sys/type.h>#include<sys/ipc.h>#include<sys/msg.h>int msgrcv(int msqid,struct msgbuf *msgp,int msgsz,long msgtyp,int msgflg)//从msqid代表的消息队列中读取一个msgtyp类型的消息,并把消息存储在msgp指向的msgbuf结构中。在成功地读取了一条消息以//后,队列中的这条消息将被删除。int read_message(int qid,long type,struct mymsgbuf *qbuf){    int result,length;    length=sizeofstruct mymsgbuf)-sizeof(long);    if((result=msgrcv(qid,qbuf,length,type,0))==-1)    return(-1);    return(result);}**eg:**#include <sys/types.h>#include <sys/msg.h>#include <unistd.h>struct msg_buf    {        int mtype;        char data[255];    };int main(){        key_t key;        int msgid;        int ret;        struct msg_buf msgbuf;        key=ftok("/tmp/2",'a');        printf("key =[%x]\n",key);        msgid=msgget(key,IPC_CREAT|0666); /*通过文件对应*/        if(msgid==-1)        {                printf("create error\n");                return -1;        }        msgbuf.mtype = getpid();        strcpy(msgbuf.data,"test haha");        ret=msgsnd(msgid,&msgbuf,sizeof(msgbuf.data),IPC_NOWAIT);        if(ret==-1)        {                printf("send message err\n");                return -1;        }        memset(&msgbuf,0,sizeof(msgbuf));        ret=msgrcv(msgid,&msgbuf,sizeof(msgbuf.data),getpid(),IPC_NOWAIT);        if(ret==-1)        {                printf("recv message err\n");                return -1;        }        printf("recv msg =[%s]\n",msgbuf.data);}

六.信号量

1.概述

信号量(又名:信号灯)与其他进程间通信方式不大相同,主要用途是保护临界资源。进程可以根据它判断是否能够访问某些共享资源。除了用于访问控制外,还可用于进程同步。

2.分类

二值信号灯:信号灯的只能取0或1,类似于互斥锁。但两者有不同:信号灯强调共享资源,只要共享资源可用,其他进程同样可以修改信号灯的值;互斥锁更强调进程,占用资源的进程使用完资源后,必须由进程本身来解锁。
计数信号灯:信号灯的值可以取任意非负值。

1)创建/打开#include<sys/types.h>#include<sys/ipc.h>#include<sys/sem.h>    int semget(key_t key,int nsems,int semflg)    //key:键值,由ftok获得    //nsems:指定打开或者新创建的**信号灯集**中将包含信号灯的数目    //semflg:标识,同消息队列2)操作    int semop(int semid,struct sembuf *sops,unsigned nsops)    //功能:对信号量进行控制。    //semid:信号量集的ID    //sops:是一个操作数组,表明要进行什么操作    //nsops:sops所指向的数组的元素个数。    struct sembuf{        unsigned short sem_num;//semaphore index in array        short sem_op;//semaphore operation        short sem_flg;//operation flags    };
0 0