进程间通信的方式

来源:互联网 发布:seo公司排行榜 编辑:程序博客网 时间:2024/06/11 01:33

进程间通信的方式

信号

信号的生成:一个进程发生了某些条件,系统产生一个事件,就是信号。

信号的发送:在终端中用命令发送信号,或者是在代码中用int kill (pid_t pid,int sig)函数发

送信号//把sig给定的信号发送给pid进程,失败时返回-1。

信号的捕获:接收到信号的进程会采取一些行动。1 默认方式SIG_DFL   2忽略 SIG_IGN   3自定义fun

信号的名称:在/usr/include/bits/signal.h中定义,常见的有:SIGINT终端中断

                                                                      SIGCHLD子进程已经停止或退出(默认作忽略处理)

信号响应方式的设置:函数原型比较复杂,具体讲解可以看评论我贴出的别人的博客。

                                            返回值是一个函数指针signal(信号值,文件指针类型的处理方式)

                                            举例:signal(SIGINT,SIG_DFL);对按Ctrl+c终止程序产生的信号SIGINT作忽略处理。忽略第一次按下的Ctrl+c。

                                                       signal( SIGUSR1,func),对某进程发来的自定义信号SIGUSR1作调用func的处理。

 

管道

有名管道

创建管道文件命令:mkfifoname (有名管道)

管道文件中的数据在内存中存放

普通文件中的数据在磁盘中存放

管道这个文件,文件的创建的信息,存放在磁盘中,管道中的数据在内存中。

管道关闭之后,(关机之后,掉电之后),管道中的数据就丢失了。(因为数据在内存中存放)

 

管道文件必须两个进程分别以读和写同时打开 ,才能打开。

打开之后,读的进程如果读不到,会阻塞住。一直等,直到那边写入,这边读到,程序才往下走。

如果其中一个写的进程Ctrl c彻底关闭了,读的进程read返回一个0,并且不再阻塞。

 

打开管道文件后,在内存中分配了一块空间,并且有头指针和尾指针,当写入的时候头指针往后移,当读取时尾指针往后移,尾指针之前的数据再也读不到了。头指针指到这块空间的末尾时,就会从内存初始重新覆盖着写。用操作普通文件的open() read()write()close()系统调用就可以操作管道文件。

 

无名管道 

通过系统调用函数pipe()创建无名管道,创建时可以同时拿到两个文件描述符,一个读一个写。

此时对通讯着的两个进程有要求,必须是通过fork创建的父子进程间才可以通讯。(因为没有名字,别的进程不知道如何打开,fork了,父进程可以将文件描述符传递给子进程)

 

#include <unistd.h>

int pipe(int file_descriptor[2])  //存放文件描述符的 整型数组

fd[0] 固定的读端

fd[1] 固定的写端

程序中实现:

写端彻底关闭,读端read返回0

读端彻底关闭,写端会引发异常,也会收到一个信号SIGPIPE,终止写端。

可以通过signal(),将信号的处理方式改变,不要终止写端。

总结:

有名管道和无名管道的区别?(有名管道可以在任意两个进程间使用

无名管道必须隶属于父子进程间使用)

有名管道和无名管道的相同点?(打开管道文件后,都是在对内存进行操作)

全双工与半双工与单工:管道(半双工)(一个进程在一个时间点只能读或者使只能写)

                                     两个进程同时操作这个有名管道文件才可以打开

                                     一个进程pipe一个无名管道文件可以先读后写

 

信号量

信号量是通过控制程序的推进速度来同步进程,为什么要同步进程呢,首先就要了解临界资源、临界区、死锁、竞争、原子操作的概念,在这里,我简单介绍一下,

临界资源:同一时刻,只允许一个(有限个)线程访问的资源。

临界区:访问临界资源的代码段。

原子操作:不可被分割(执行的时候不可被中断)的(一组)操作

信号量:特殊的变量,只能取正整数值,

作用:    信号量是控制线程对临界资源的访问,保证同一刻只有一个线程访问。当临界区域可用时,p操作将信号量-1,表示临界区正在被使用。如果信号量值=0,那么再进行-1操作时会阻塞。离开临界区时,用v操作对信号量+1,使临界区再次变为可用。并且+1v  -1p是原子操作。

目的:    同步进程。

方法:    控制程序的推进速度。

使用方法:


semget():全新创建一个信号量,或者获取一个已有的信号量的值

         Key:一个整型值,两个进程互相知道这个整型值。

         Num_sems:信号量的个数,一个或者多个

         Sem_flags:标志位,全新创建出一个信号量,失败返回-1

semop():对信号量的值进行修改  +1V  -1P


semctl():对信号量做控制,为信号量赋初始值。初始值代表可用的资源数量。

初始化一个信号量:,  或者可以对信号量进行销毁


一个例子:

#include "sem.h"static int semid=0;void sem_init(){semid=semget((key_t)1234,1,IPC_CREAT|IPC_EXCL|0600); //全新创建一个信号量if(semid == -1) //如果返回-1 ,代表已有这个信号量,或者出错{semid=semget((key_t)1234,1,IPC_CREAT|0600); /如果已有,获取它if(semid==-1)//如果还是-1   代表出错{perror("semget error!\n");}}Else  //已全新创建出一个信号量{union semun a;a.val=1;if(semctl(semid,0,SETVAL,a)==-1)  //为信号量附初值perror("semctl error!\n");}}void sem_p()   //  p操作 -1{struct sembuf buf;    //初始化结构体buf.sem_num=0;   //改变第几个信号的值,,如果只有一个信号量,则是第0个buf.sem_op=-1;     //要改变的值。buf.sem_flg=SEM_UNDO;   //如果程序未释放,操作系统帮忙释放if(semop(semid,&buf,1)==-1){perror("p error");}}void sem_v()   //  v操作   +1{struct sembuf buf;buf.sem_num=0;buf.sem_op=1;   //要改变的值buf.sem_flg=SEM_UNDO;if(semop(semid,&buf,1)==-1){perror("v error");}}void sem_destory() //销毁的时候,不存在销毁信号量中哪一个,只会一次性销毁一组信号量  ,第二个参数是无效值。{union semun sem_union;   //这行好像不是必要if(semctl(semid,0,IPC_RMID,sem_union)==-1)    //最后一个参数也不是必要{perror("semctl destory error!\n");}}

共享内存

共享内存与管道不同,管道必须两个进程同时使用,而且读进程读完的数据,指针再也获取不到。一次性。

共享内存只要存在,就可以让进程使用,并且其中的数据,在内存被移除之前都是存在的。使用起来更灵活。

使用方法:


  •  Shmget ()创建共享内存空间,或者获取一块已有的共享内存空间


  •  Shmat() 把共享内存映射到当前进程的虚拟地址空间中


第二个参数一般传空 NULL指针  意为让系统选择共享内存出现的地址

第三个参数  一般 也给空  0   表示一个标志位,可以不予设置

有两个可选项  第一个SHM_RND 

                                     第二个SHM_RDONLY  只读

  •  Shmdt() 断开映射


本进程不再能够使用,但是共享内存依然存在,别的进程还可以用。

其他进程通过相同的key值shmget()获得,

  •  Shmctl() 移除共享内存空间

 

 

消息队列

消息队列与命名管道有着许多相似的地方,消息队列的优点是它独立与发送和接收的进程而存在,命名管道必须两个进程一起操作,一方读另一方写。

Int msgget(key_t key ,int msgflg);//创建和访问一个消息队列。

参数: ( 用一个键值来命名某个特定的消息队列,第二个参数是IPC_CREAT)

返回值:代表队列标识符的正整数,失败时返回-1;

Int msgid=msgget((key_t)1234,0666|IPC|CREAT);

Int msgsnd(int msgid,const void*msg_ptr,size_t msg_sz,int msgflg);

第一个参数是msgget返回的消息队列标识符

第二个参数是指向准备发送消息的指针,指针类型是一个结构体,必须自己定义

Struct  my_message{long int message_type;char buff[MAX];};//但必须以长整型变量开始。Struct my_message some_data;

第三个参数是要发送的消息的长度。

第四个参数是IPC_NOWAIT,如果消息队列满了,则函数立刻返回-1,不发送消息。

Msgsnd(msgid,(void *)&some_data,MAX,0);

Int msgrcv(int msgid,void *msg_ptr,size_tmsg_sz,long int msgtype,int msgflg);

         第一个参数是消息队列标识符

         第二个参数是指向准备接受消息的指针,也是一个结构体类型。

         第三个参数是指针指向的消息的长度。

         第四个参数一般取0,表示按照消息发送的顺序来接受他们。

第五个参数是

Msgrcv(msgid,(void *)&some_data,bufsize,msg_to_receive,0);

Int msgctl(int msgid,int command,structmsqid_ds *buf);

Struct msqid_ds{uid_t msg_perm.uid;

                            Uid_t msg_perm.gid;

                            Mode_t msg_perm.mode};

Msgctl(msgid,IPC_RMID,0);//删除消息队列。一方删除就可以了。

套接字

套接字既可以在本地进程间通信,也可以跨网络,在不同主机上的进程间通信。具体使用方法看我另外一篇转载的博客,写的很细。