进程通信

来源:互联网 发布:java 项目开发 图 编辑:程序博客网 时间:2024/06/05 11:45
进程通信:
目的:
1.数据传输:一个进程需要将它的数据发送给另一个进程。
2:资源共享:多个进程之间共享同样的资源。
3:事件通知:一个进程需要向另一个或另一组进程发送消息,通知它们发生了某种事件。
4:进程控制:有些进程需要完全控制另一个进程的执行,此时控制进程希望能够拦截另一个进程的的所有操作,并能够及时知道它的改变。(比如在调试该进程时需要对别的进程进行控制)。

IPC由以下几部分发展而来:
1.UNIX进程间通信
2.基于system V进程间通信
3.POSIX进程间通信
(portable operating system interface )
IE的组织定义了一些标准,如果程序是按照这个标准写的,那么可以在不同的平台上执行。表示可移植操作系统接口,是为了提高UNIX应用程序的可移植性。
system V
是Linux的一个分支,是Unix 操作系统众多版本中的一个分支。
现在Linux使用的进程间通信
分类:
1.管道(pipe)和有名管道(FIFO)
2.信号(signal)
3.消息队列
4.共享内存
5.信号量
6.套接字(socket)


管道通信
定义:管道是单向的,先进先出的,它把一个进程的输出和另一个进程的输入连接在一起,一个进程(写进程)在管道的尾部写入数据,另一个进程(读进程)从管道的头部读出数据。
管道包括无名管道和有名管道两种,前者用于父进程和子进程的通信,后者可用于运行于同一系统中的任意两个进程之间的通信。
无名管道由pipe()创建:
int pipe(int filedis[2]);
当一个管道建立后,它会创建两个文件描述符:filedis[0]用于读管道,filedis[1]用于写管道。
关闭管道只需要普通的close函数,将头部和尾部的文件描述符关掉。
管道用于不同进程间通信,通常先创建一个管道,再通过fork函数创建一个子进程,该子进程会继承父进程所创建的管道。
父进程会通过fd[1]从管道的尾部写入数据,子进程通过fd[0]从管道的头部读入数据。
注意事项:必须在系统调用fork前进行pipe的调用,否则子进程无法继承父进程的文件描述符。如果先调用pipe,后调用fork,那么会产生两个管道,子进程无法与父进程之间通信。
有名管道和无名管道基本相同,但也有不同点,无名管道只能由父子进程使用;但是通过命名管道,不相关的进程之间也能交换数据。
int mkfifo(char *pathname,mode_t mode);属性和文件创建时的属性基本一致。当打开FIFO时,使用了O_NONBLOCK,访问要求无法满足时不阻塞,立刻出错返回,errno是ENXIO。
当不适用O_NOBLOCK,访问要求无法满足时进程将阻塞,试图读取空的FIFO时,将导致进程阻塞。
几个错误返回码的含义:

EEXIST:不要因为已经存在同名管道而导致打不开管道的错误出现
EEAGAIN:以O_NOBLOCK读取数据时,连续进行read操作,而没有数据可读时,返回EEAGAIN。
O_EXCL:确保创建一个文件没有其它的同名文件。



信号通信

当某种信号出现时,将按照下列方式中的一种进行处理。
1.忽略此信号
大多数信号都按照这种方式来处理,但有两种信号绝对不能被忽略,它们是:SIGKILL和SIGSTOP。这两种信号不能忽略的原因是它们向超级用户提供了一种终止或者停止进程的方法。
2.执行用户希望的动作
通知内核在某种信号发生时,调用一个函数,在用户函数中,执行用户希望的处理。
3.执行系统默认动作
对大多数信号的默认系统动作是终止该进程。
kill既可以向自身发送信号,也可以向其他进程发送信号,与kill不同的是,raise函数是向进程自身发送信号。
#include <sys/types.h>
#include <signo.h>
int kill(pid_t  pid,int signo );
int raise (int signo);
alarm函数
使用alarm函数可以设置一个时间值(闹钟时间),当你所设置的时间到了时,产生SIGALARM信号,如果不捕捉此信号,默认动作是终止该进程。
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
pause函数
pause函数使调用进程挂起直至捕捉到一个信号
#include <unistd.h>
int pause(void);
只有执行了一个信号处理函数后,挂起才结束。
信号的处理:
当系统捕捉到某个信号后,可以忽略信号或是使用指定的处理函数来处理该信号,或者使用系统默认的方式。
信号的处理方法有两种,一种是signal函数,另一种是使用信号集函数组。
signal
#include <signo.h>
void (  *signal  (   int signo,void(*func)(int)     )   )(int)
typedef void(*sighandler_t)(int)
 sighandler_t signal(int signum,sighandler_t handler))
Func可能的值是:
SIG_IGN忽略
SIG_DFL:按系统默认方式处理
信号处理函数名:使用该函数处理





共享内存
共享内存:共享内存是被多个进程共享的一部分物理内存,共享内存是进程间共享数据的一种最快的方法,一个进程向共享内存区域写了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容。
共享内存创建分为两个步骤:
一:创建共享内存,使用shmget函数
二:映射共享内存,将这段创建的共享内存映射到具体的进程空间去,使用shmat函数。
创建:
int shmget(key-t key,int size,int shmflag);
key标示共享内存的键值:0/IPC_PRIVATE,当key的取值为IPC_PRIVATE,则函数shmget()将创建一块新的共享内存。如果key的取值为0,而参数shmflg中又设置IPC_PRIVATE,,则同样会创建一块新的共享内存。

返回值:如果成功,返回共享内存标示符;如果失败,返回-1。

映射:
int shmat(int shmid,char *shmaddr,int flag)
参数:
shmid:shmget函数返回的共享存储标识符。
shamaddr:通常不传入值,而是利用它来获取值的,如设置为0,就是让它通过系统自动分配一个合适的地址。自己找的不一定能用。
flag:决定以什么方式来确定映射的地址,通常为0。

返回值:如果成功,则返回共享内存映射到进程中的地址,如果失败,则返回-1。
对shmaddr的认识:
(进程一和进程二的共享内存)共享内存在进程一中的地址和进程二中的地址不一样,这就是因为共享内存被映射到了不同进程中的不同位置,这样才可以对其进行操作。
当一个进程不再需要共享内存时,需要把它从进程空间中脱离。
int shmdt(char *shamaddr);

消息队列
进程间通信的目的:数据传输,共享资源,事件通知,进程控制
六种进程间通信的方式:管道通信,信号通信,共享内存,消息队列,信号量和socket通信
定义:
UNIX早期通信机制之一的信号能够传输的信息量有限,管道则只能传送无格式的字节流,这给应用程序的开发带来了不便。消息队列(也叫做报文队列)则克服了这些缺点。
发展:
消息队列就是一个消息的链表。可以把消息看作一个记录,具有特定的格式,进程可以向其中按照一定的规则添加新消息;另一些进程则可以从消息队列中读走消息。和管道通信有点像,读走了之后,消息就不存在了。
目前主要有两种类型的消息队列:
POSIX(可移植操作系统接口)消息队列以及系统V消息队列。
系统V消息队列目前被大量使用。
持续性:
系统V消息队列是随内核持续的,只有在内核重启或者人工删除时,该消息队列才会删除。
要求每一个消息队列都在系统范围内对应唯一的键值,所以要获得一个消息队列的描述值,必须得到该消息队列的键值。
键值:
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(char *pathname,char proj);
功能:
返回文件名对应的键值
pathname:文件名
proj:项目名(不为0即可)
得到键值之后,可以以此来获得消息队列的描述字。

打开/创建
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
    int msgget(key_t key,int msgflag);
key:键值,由ftok获得
msgflg:标志位
返回值:与键值key相对应的消息队列描述字。
标志位:
IPC_CREAT:创建新的消息队列
IPC_EXCL:与IPC_CREAT 一同使用,表示如果创建的消息队列已经存在,则返回错误
IPC_NOWAIT:读写消息队列要求无法得到满足时,不阻塞。

创建:
¥如果没有与键值key相对应的消息队列,并且msgflag中包含了IPC_CREAT标志位
¥key参数为IPC_PRIVTE.

发送消息:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgsnd(int msqid,struct msgbuf* msgp,int msgsz,int msgflg)
功能:向消息队列中发送一条消息
msqid:消息队列的描述符,即已打开的消息队列id
msgp:存放消息的结构
msgsz:消息数据长度
msgflg:发送标志,有意义的msgflg标志为IPC_NOWAIT,指明在消息队列没有足够空间容纳要发送的消息时,msgsnd是否等待。
stuct msgbuf
{
    long mtype;/*消息的类型
    char mtext[1];/*消息数据的首地址*/
}
当把数据发送到消息队列了以后,其他进程就可以从消息队列中取相应的数据了。


接受消息:
#include<sys/types.h>
#incude<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=sizeof(struct mymsgbuf)-sizeof(long);/*为什么要减去long,这是因为在该结构体中,存放了两种类型的变量,数据和数据类型,减去数据类型,剩下的才是数据的长度*/
    if((result=msgrcv(qid,qbuf,length,type,0))==-1);
    return (-1);
    return(result);
}




信号量
信号量与其他进程间通信方式不大相同,主要用途是保护临界资源。进程可以根据它判定是否能够访问某些共享资源。除了用于访问控制外,还可用与进程同步。
假设A、B两进程都想要访问S资源,给S资源安排一个信号量,初始值为1,表示可以访问。
假如A首先来检查,发现信号量大于0,就会减去1,变成了0,并去访问该资源。
B来检查的时候,发现信号量等于0,就不能访问该资源。
A访问完后,就会把信号量加1(释放信号量),这时候把B唤醒,B就可以获取该信号量了,把信号量的值减1。


分类:
二值信号量:信号量的值只能取0和1,表示在任何时刻最多只能有一个进程来访问它,或者说明这种类型的资源只有一个。
计数信号量:信号量的值可以取任意非负值。一直减,减到0之后其他进程就不能来访问了。
在一个信号量数组里面可能有多个信号量。
#include <sys/types.h>
#include <sys/ipc.h>
#Include <sys/sem.h>
    int semget(key_t,int nsems,int semflg);
key:键值,由ftok获得
nsems:指定打开或者新创建的信号量集合中将包含信号量的数目
semflg:标识、同消息队列

操作:
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*/
};
sem_num,要操作的信号量在信号集中的编号,第一个信号的编号是0;
sem_op=1,那么是释放信号量
sem_op=-1,应该是获取信号量
sem_op:如果其值为正数,该值会加到现有的信号量值中,通常用于释放信号量;如果sem_op的值为负数,而其绝对值又大于信号的现值,操作将会阻塞,直到信号值大于或等于sem_op的绝对值,通常用于获取信号量;如果sem_op的值为0,则操作将暂时阻塞,知道信号量的值为0.
sem_flg:信号操作标志,可能的选择有两种:
IPC_NOWAIT:对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息。
IPC_UNDO:程序结束时(不论正常或不正常)释放信号量,这样做的目的在于避免程序在异常情况下结束时未将锁定的资源解锁,造成该资源永远锁定。






原创粉丝点击