进程间通信 IPC(InterProcess Communication)基本机制:信号、管道及命名管道、消息队列、共享主存、信号量、套接字。
信号:全称软中断信号,是在软件层次上对中断机制的一种模拟,它也是进程间通信机制中唯一的异步通信机制。Linux信号处理函数可分为信号安装函数、信号发送函数和信号集操作函数。
信号安装函数signal(int signum,void(*handler)(int))用来设置某个信号的处理函数,其中参数signum指出要设置处理函数的信号,参数handler是处理函数,可以是SIG_IGN(忽略该信号)、SIG_DEL(使用默认信号函数)或自定义信号处理函数。
信号发送函数:信号发生有两个来源 硬件原因(如按键键盘),软件原因(如函数或命令发出信号)。其中有5个最常见的发出信号函数 kill()、raise()、alarm()、setitimer()、和pause()。
kill(pid_t pid,int sig) 函数用来想进程发送一个信号。
raise(int sig) 函数用来向自身发送函数。
alarm(unsigned int seconds) 函数用来设置一个定时器,当定时器技术到达时,将发出信号SIGALARM给进程,信号安装函数接受到该信号后进行相应的处理。
管道和命名管道:最适合在进程间实现生产者和消费者的交互。管道被看作是打开的文件,但在已安装的文件系统中没有相应的映像,使用起来比临时文件更方便。
无名管道:只能用于一个进程家族之间通信,父子之间,兄弟之间等等,调用pipe(int fds[2]),之后会创建两个文件描述符fds[0]和fds[1],其中fds[0]固定用于读管道,fds[1]固定用于写管道。调用fork()函数后,子进程会继承父进程所创建的管道,此时父子进程分别拥有自己的读写管道,如果将父进程的写端fds[1]和子进程的读端fds[0]关闭,则将建立一条“子进程写入父进程读出”的通道。
命名管道:克服了匿名管道只能用于具有亲缘关系的进程间通信的限制,命名管道通过mkfifo(my_fifo,O_CREAT|O_EXCL)函数创建,之后对命名管道my_fifo的操作可用一般文件I/O函数。打开命名管道fd= open(MYFIFO,O_RDWR)后得到文件描述符,之后读写就可用read(fd,buffer,size)之类函数进行了。
消息队列就是消息的一个链表,它允许一个或多个进程向它写消息,一个或多个进程从中读消息。Linux维护了一个消息队列向量表:msgque,来表示系统中所有的消息队列。其定义如下:
struct msqid_ds *msgque[MSGMNI];
该向量表中的每一个元素都是一个指向msqid_ds数据结构的指针,而一个msqid_ds数据结构完整地描述了一个消息队列。
当创建消息队列时,一个新的msqid_ds数据结构被从系统内存中分配出来,并被插入到msgque 向量表中。
消息队列的实现包括:创建或打开消息队列、添加消息、读取消息和控制消息队列这4个操作。
创建、打开消息队列的函数 msgget();
添加消息的函数 msgsed();
读取消息的函数 msgrcv();
控制消息队列的函数 msgctl();
发送端代码:
- int main()
- {
- char message[512]; //要通信的信息
- key = ftok(".",'a'); //消息队列的键值,多个线程通过它访问同一个消息队列
- qid = msgget(key,IPC_CREAT|0666); //创建消息队列
- while(1)
- {
- fgets(message,512,stdin); //获取键盘输入作为消息
- msgsed(qid,&message,strlen(message)); //添加消息到消息队列
- if(srtncmp,"quit",4) == 0 //检测消息前4个字符,如果为“quit”,则退出
- break;
- }
- exit(0);
- }
接受端代码:
- int main()
- {
- char message[512]; //要通信的信息
- key = ftok(".",'a'); //消息队列的键值,多个线程通过它访问同一个消息队列
- qid = msgget(key,IPC_CREAT|0666); //创建消息队列
- do
- {
- memset(message,0,512);
- msgrcv(qid,(void*)message,sizeof(message),0,0); //添加消息到消息队列
- }while(srtncmp,"quit",4); //检测消息前4个字符,如果为“quit”,则退出
- msgctl(qid,IPC_RMID,NULL); //从系统内核中移走消息队列
- exit(0);
- }
共享内存:因为所有进程共享同一块内存,共享内存在各种进程间通信方式中具有最高的效率。
进程通过调用shmget(key_t key, int size ,int shmflg)(Shared Memory GET,获取共享内存)来分配一个共享内存块。该函数的第一个参数是一个用来标识共享内存块的键值。彼此无关的进程可以通过指定同一个键以获取对同一个共享内存块的访问。第二个参数为共享内存大小。第三个参数 同open()函数的权限为,也可用八进制表示法。
要让一个进程获取对一块共享内存的访问,这个进程必须先调用 shmat( int shmid , const void *shmaddr, int shmflg)(SHared Memory Attach,绑定到共享内存)。将 shmget 返回的共享内存标识符 SHMID 传递给这个函数作为第一个参数。第二个参数表示将共享内存映射到指定地址。
当一个进程不再使用一个共享内存块的时候应通过调用 shmdt( const void *shmaddr)(Shared Memory Detach,脱离共享内存块)函数与该共享内存块脱离。参数shmaddr 为被映射的共享内存段地址。
信号量:是用来解决进程间的同步与互斥问题的一种进程间通信机制,包括一个称为信号量的变量和在该信号量下等待资源的进程等待队列,以及对信号量进行的两个原子操作(PV操作)。信号量指的是当前可用的该资源的数量,若它等于0则意味这目前没有可用的资源。
信号量编程步骤
1)semget(key_t key, int nsms ,int semflg ) 创建信号量或获取系统已存在的信号量。
2)semctl( int semid , int semnum , int cmd, union semun arg) 初始化信号量。
3)semop(int semid, struct sembuf *sops, size_t nsops) 进行信号量的PV操作。
4)用semctl()函数删除不需要的信号量。
疑问:在 Linux 上,有两类信号量。
第一类是由 semget/semop/semctl API 定义的信号量的 SVR4(System V Release 4)版本。
第二类是由 sem_init/sem_wait/sem_post/interfaces 定义的 POSIX 接口。 它们具有相同的功能,但接口不同。
套接字:套接字编程模型