进程间通信

来源:互联网 发布:js截取到某个字符 编辑:程序博客网 时间:2024/06/03 07:50

进程间通信,首先我们需要知道进程间为什么要通信。大概可以分为下面几点:

1、数据传输:一个进程需要把数据传给另一个进程。

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

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

4、进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有操作,并能够及时知道它的状态改变。


那么进程间通信有哪些通信方式呢,这也是我今天主要说的,进程间通信主要分为:管道通信,共享内存,消息队列,信号通信。

1、管道通信

什么是管道呢?管道是一种单向传输,先进先出的进程间通信,并且管道里的数据是一次性的,我们可以把它想象成水龙头,数据呢,就是里面的水。管道通信分为两种:无名管道和有名管道。前者用于父子进程之间相互通信,后者用于同一系统下任意两个进程之间的通信。

首先我们先说无名管道。无名管道由pipe()创建

函数原型:int pipe(int filedis[2]);一个管道被创建好当一个管道建立时,它会创建两个文件描述符:
filedis[0]用于读管道,filedis[1]用于写管道。一般定为fd[0],fd[1],所以要创建一个数组用来存放两个文件描述符。

如何在父子进程之间通信。

先pipe再fork,父进程先pipe一个管道,再fork一个子进程,但是这样的管道的每一端会有两个读写在工作,而我们只要一个,所以父进程关闭读fd[0],子进程关闭写fd[1]。

下面是程序:

// 子进程通过管道从父进程接收数据void child_do(int *fd){// 将管道的写端关闭close (fd[1]);char buf [SIZE];while (1){// 从父进程读取数据int ret = read (fd[0], buf, SIZE-1);if (ret == -1){perror ("read");break;}buf[ret] = '\0';printf ("子进程读到 %d 字节数据: %s\n", ret, buf);}// 关闭读端close (fd[0]);}
// 父进程通过管道向子进程发送数据void father_do(int *fd){// 将管道读端关闭close (fd[0]);char buf[SIZE];while (1){fgets (buf, SIZE, stdin);// 向子进程发送数据int ret = write (fd[1], buf, strlen(buf));printf ("父进程发送了 %d 字节数据\n", ret);}// 关闭写端close (fd[1]);}
int main(){int fd[2];// 创建管道int ret = pipe(fd);if (ret == -1){perror ("pipe");return -1;}// 创建子进程pid_t pid = fork();switch (pid){case -1:perror ("fork");break;case 0:   // 子进程child_do(fd);break;default:father_do(fd);break;}return 0;}

有名管道(FIFO)

函数原型:int mkfifo(const char *pathname, mode_t mode);

pathname: FIFO文件名
mode:属性(同文件操作)
 
一旦创建了一个FIFO,就可用open打开它,一般的文件访问函数(close、read、write等)都可用于FIFO。

当打开FIFO时,非阻塞标识(O_NONBLOCK)将对以后的读写产生影响:
1、没有使用O_NONBLOCK:访问要求无法满足时进程将阻塞。如果试图读取空的FIFO,将导致进程阻塞。
2、使用O_NONBLOCK:访问要求无法满足时不阻塞,立刻出错返回。errno是ENXIO。

int main(){int ret = mkfifo("/home/mkfifo", 0777);if (ret == -1){perror ("mkfifo");return -1;}return 0;}
关闭管道只需要将两个文件描述符关闭即可,可以使用普通的close函数逐个关闭。


2、共享内存

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

共享内存的实现:(1)创建共享内存(2)映射到本地空间。当然还包括接触映射,和删除共享空间。

(1)创建共享内存

函数原型:int shmget(key_t key, int size, int shmflg)

key:共享内存的名字

size:开辟多大空间

shmflg:权限

返回值:成功返回一个id用来操作共享内存

        错误返回 -1

关于第二个参数,不要用int型什么,一般用一个结构体,如下面:

typedef struct _shm{int flag;char msg[256];}SHM;
int main(){// 1、创建或者获取一个共享内存int shmid = shmget((key_t)1234, sizeof(SHM), 0666 | IPC_CREAT);if (shmid == -1){perror ("shmget");return -1;}// 2、将共享内存映射到当前的进程空间SHM* pshm = (SHM*)shmat(shmid, NULL, 0);if(pshm == (SHM*)-1){perror ("shmat");return -1;}strcpy (pshm->msg, "hello");// 解除共享内存映射,解除是值当前进程不能再使用共享内存shmdt(pshm);// 共享内存的删除shmctl(shmid, IPC_RMID, NULL);return 0;}

3、信号量(信号量并不属于进程间通信,而是属于进程同步的一种机制)
  信号量是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。最简单的信号量是只能取0和1的变量,这也是信号量最常见的一种形式,叫做二进制信号量。而可以取多个正整数的信号量被称为通用信号量。这里主要讨论二进制信号量。

由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:
P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行
V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.

举个例子,就是两个进程共享信号量sv,一旦其中一个进程执行了P(sv)操作,它将得到信号量,并可以进入临界区,使sv减1。而第二个进程将被阻止进入临界区,因为当它试图执行P(sv)时,sv为0,它会被挂起以等待第一个进程离开临界区域并执行V(sv)释放信号量,这时第二个进程就可以恢复执行。

信号量的创建:

函数原型:int semget(key_t key, int num_sems, int sem_flags);  

它的作用是改变信号量的值:int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops); 

该函数用来直接控制信号量信息:int semctl(int sem_id, int sem_num, int command, ...); 


4、消息队列

消息队列可以看成链表,是一种类似链表的进程间通信,因为每个消息都有自己的类型,所以在入消息队列时,要给定类型,取消息的时候,指明取哪个类型的消息,消息是一次性的,读完一次就没有了。

键值:

消息队列的内核持续性要求每个消息队列都在系统范围内对应唯一的键值,所以,要获得一个消息队列的描述符,必须提供该消息队列的键值。

函数原型:key_t ftok(char *pathname, char proj);
功能:返回文件名对应的键值。
pathname:文件名
proj:项目名(不为0即可)


创建函数:

函数原型:int msgget(key_t key, int msgflg)

key: 键值,由ftok获得
msgflg:标志位
返回值:与键值key相对应的消息队列的描述符。

msgflg取值:IPC_CREAT。创建新的消息队列

发送消息:

int msgsnd(int msqid, struct msgbuf * msgp, int msgsz, int msgflg)功能:向消息队列中发送一条消息msqid:消息队列描述符msgp:消息队列指针,指向存放消息的结构msgsz:消息数据长度msgflg:发送标志,有意义的msgflg标志为IPC_NOWAIT,指明在消息队列没有足够空间容纳要发送的消息时,msgsnd是否等待 消息格式:struct msgbuf{     long mtype;     // 消息类型 > 0     char mtext[1];  // 消息数据的首地址  }

接收消息:

int msgrcv(int msqid, struct msgbuf* msgp, int msgsz, long msgtp, int msgflg)功能:从msqid代表的消息队列中读取一个msgtyp类型的消息,并把消息存储在msgp指向的msgbuf结构中。在成功的读取了一条消息以后,队列中的这条消息将被删除。 

对列控制:int msgctl(int msgid, int command, struct msgid_ds *buf);   command是将要采取的动作,它可以取3个值,    IPC_STAT:把msgid_ds结构中的数据设置为消息队列的当前关联值,即用消息队列的当前关联值覆盖msgid_ds的值。    IPC_SET:如果进程有足够的权限,就把消息列队的当前关联值设置为msgid_ds结构中给出的值    IPC_RMID:删除消息队列 buf是指向msgid_ds结构的指针,它指向消息队列模式和访问权限的结构。msgid_ds结构至少包括以下成员:struct msgid_ds  {      uid_t shm_perm.uid;      uid_t shm_perm.gid;      mode_t shm_perm.mode;  };  

成功时返回0,失败时返回-1.

4、信号

signal(int signum,signhandler_t handler);

信号处理函数:

void handle (int signum){       printf("捕捉到信号ctrl+c\n");}int main (){       signal(SIGINT,handler);       while (1);       return 0; }



alarm 定时器

定时器是一次性的,所以我们需要定时器重置

alarm(unsigned int seconds);


其实通过signal,我们可以利用它来避免僵尸进程,子进程在死去后,会发出信号,被signal捕捉到,然后在信号处理函数handle中为子进程收尸,只有一个进程,我们可以用wait,假如有10个子进程一起退出,用waitpid非阻塞。



原创粉丝点击