1. 进程间通信的目的:
①数据传输:一个进程的数据发送给另一个进程;
②资源共享:多个进程之间共享同样的资源;
③通知事件:一个进程需要向另一个或一组进程发送消息,通知它们发生了某种事件;
④进程控制:有些进程希望完全控制另一个进程的执行,此进程希望能够拦截另一个进程的全部操作,并能够及时直到它的状态发生了什么改变;
2. Linux进程间通信(IPC)由三个部分发展而来:①Unix进程间通信;②基于system V系统进程间通信;③POSIX进程间通信;
system V是贝尔实验室开发的一个Unix操作系统分支;
POSIX全称可移植操作系统接口,由IEEE发布该标准,各主流操作系统均支持,目的是提高应用程序的可移植性;
3. Linux在进程间通信的方式包括:
①无名管道(PIPE)、有名管道(FIFO):可能会装满、为空,即读写可能会阻塞,其收发消息都要经过缓冲区,当做特殊设备文件对待;同步通信特性;
②信号(signal):异步通信特性;
③消息队列:消息链表,比管道的容量大;
④共享内存:最快速,效率最高;
⑤套接字(socket)(大材小用):即可多机,也可以单机通信;
4. 管道通信
(1)简介
①:管道本质是一个队列,是单向的、先进先出,一个进程在管道的尾部写入数据,另一个进程从管道的头部读出数据;
②数据被一个进程读出后,对应数据将从管道中删除;
③流控制机制:进程在读空管道时,进程将阻塞;进程在向满管道写入数据时,进程也会阻塞;
④配合进程使用时,需要在创建子进程前调用管道创建函数,将管道创建好,这样子进程才可以继承对应的管道;
⑤管道分为无名管道、有名管道,区别如下:
无名管道PIPE | 有名管道FIFO | |
生命周期 | 随进程持续(不属于内核) | 随内核持续 |
作用范围 | 用于父子进程之间的通信 | 用于任意两个进程之间通信 |
(2)函数实现
无名管道创建
函数原型 | int pipe(int filedis[2]); |
示例 | int pipe_fd[2]; //注意提前定义数组,一定要是2个元素; pipe(pipe_fd); //此条命令之后,2个元素分别代表了读写管道,可以把每个元素当做一个文件描述符:可以理解为它创建了两个文件,分别将文件描述符赋值给了2个元素,并且默认打开了这两个文件; ①pipe_fd[0]:read读管道 ②pipe_fd[1]:write写管道 注意用完之后要分别关闭管道: ①close(pipe_fd[0]); ②close(pipe_fd[1]); |
参数 | filedis[2]:提前定义好的数组(只有两个元素); |
返回值 | 0:创建成功 -1:创建失败 |
头文件 | #include<unistd.h> |
特性:①数据从一个管道读出后,该数据将被从管道中删除;
②管道空时,再读会阻塞;管道满时,再写会阻塞;若要更改为非阻塞,配合
③通常先在进程创建前生成管道,再创建子进程,这样子进程就可以继承管道,父子进程间就可以通信;这种通信是半双工方式;即fork()前用pipe()创建管道;
无名管道创建
函数原型 | int mkfifo(const char * pathname, mode_t mode); |
示例 | vim fifo.dada; //提前创建一个fifo文件(空的,没有内容),就是普通的空文件; mkfifo("./fifo.data", 0666); if(fork>0) //在父进程中 fd = open("./fifo.data",O_WRONLY); write(fd, "hello", 6); else //在子进程中 fd = open("./fifo.data",O_RDONLY); read(fd, buff, 10); printf("%s", buff); 详见下图 |
参数1 | pathname:FIFO文件名 |
参数2 | mode:属性(与open文件操作的属性一致) |
返回值 | 0:创建成功 -1:创建失败 |
头文件 | #include<sys/types.h> #include<sys/stat.h> |
5. 信号通信
(1)简介
①信号(signal)机制是Unix中最老的进程间通信方式,信号的来源主要有:用户按键产生信号、硬件异常产生信号、进程用kill命令将信号发送给另一个进程等;
②信号类型,常用的几种信号:
SIGINT | 来自键盘的中断信号(CTRL-C) |
SIGKILL | 该信号结束接收信号的进程 |
SIGTERM | kill命令发出的信号 |
SIGCHLD | 标识子进程停止或结束的信号 |
SIGSTOP | 来自键盘(CTRL-Z)或调试程序的停止执行信号 |
SIGHUP | 从终端上发出的结束信号 |
③信号的处理方式:三种
忽略此信号 | SIGKILL、SIGSTOP不能被忽略,其他的默认忽略; |
执行用户指定的动作 | 在用户函数中,执行用户希望的处理 |
执行系统默认动作 | 按照默认进行 |
(2)函数实现:发送信号的函数有两个:kill、raise;区别就在于:
kill | raise |
既可以向自身进程发送信号,也可以向指定的进程发送信号 | 只可以可以向自身进程发送信号 |
函数原型 | int kill(pid_t pid, int signo); int raise(int signo); |
示例 | / |
参数1 | pid,指定的进程号,有4种情况: ①pid>0:将信号发送给进程id为pid的进程; ②pid==0:将信号发送给同组的进程; ③pid<0:将信号发送给其进程组id等于pid绝对值的进程; ④pid==-1:将信号发送给所有进程; |
参数2 | signo,信号种类 |
返回值 | 0:成功 -1:失败 |
头文件 | #include<sys/types.h> #include<signal.h> |
alarm
使用alarm可以设置一个闹钟时间,时间到了,产生SIGALRM信号,如果不捕捉该信号,默认动作是终止该进程;需要配合pause函数使用;每个进程只能有一个闹钟起作用;
函数原型 | unsigned int alarm(unsignde int seconds) |
示例 | / |
参数1 | seconds:指定的秒数; |
返回值 | 0:成功 >0:被后一个闹铃覆盖后前一个闹铃剩余的时间; |
头文件 | #include<unistd.h> |
pause
作用是使调用该函数的进程挂起,直至捕捉到一个信号,只有执行了一个信号处理函数后,挂起才结束;
函数原型 | int pause(void) |
示例 | / |
参数 | 无参数; |
返回值 | 0:成功 >0:被后一个闹铃覆盖后前一个闹铃剩余的时间; |
头文件 | #include<unistd.h> |
(3)信号处理函数,一般采用signal函数;
函数原型 | void ( *signal ( int signo, void ( *func ) ( int ) ) ) ( int ) ; |
示例 | signal ( SIGINT, myfunc); //该进程得到SIGINT信号后去执行myfunc自定义函数,而SIGINT就是myfunc的参数; 详见下图; |
参数1 | signo:信号种类(如SIGINT等); |
参数2 | func:自定义函数的函数名; |
返回值 | 0:成功 -1:失败 |
头文件 | #include<signal.h> |
6. 共享内存通信
(1)简介
①共享内存是指被多个进程共享的一部分物理内存(存在于内核中,其地址映射到进程中),共享内存是进程间共享数据的一种最快的方法;
②实现分为4个步骤:创建共享内存(shmget函数),映射共享内存(shmat函数)、解除映射(shmdt函数)、关闭共享内存(shmctl函数);
(2)函数实现(课件中的shm_comm.c较全面)
shmget:创建共享内存
函数原型 | int shmget (key_t key, int size, int shmflg); |
示例 | / |
参数1 | key,标识共享内存的键值,可以是指2种情况: ①IPC_PRIVATE:此时由内部分配一块新的共享内存; ②0,那么第三个参数shmflg也要设置为IPC_PRIVATE,那么等同于第一个参数直接设置为IPC_PRIVATE; |
参数2 | size,需要的共享内存大小,以字节为单位; 系统内核实际分配会以一页(4k)个字节为单位分配; |
参数3 | shmflg,通常设置为IPC_CREAT,设置共享内存的文件属性权限,举例: 0666|IPC_CREAT; |
返回值 | >0:成功,返回共享内存id,称为共享内存标识符,就像文件描述符一样; -1:失败 |
头文件 | #include<shm.h> |
shmat:映射共享内存
函数原型 | char* shmat (int shmid, char * shmaddr, int flag); |
示例 | / |
参数1 | shmid,就是shmget返回的共享内存标识符; 整体上就是把第一个参数的共享内存地址映射到第二个参数,即进程; |
参数2 | shmaddr,当前进程的内核地址,一般不指定,直接写NULL; |
参数3 | flag,以什么方式来确定映射的地址,一般写0; 很少写SHM_RDONLY,表示只读打开; |
返回值 | 成功:返回该进程中被映射到的地址,可以设置一个char* 变量shm_addr接收;而后就可以当做字符串进行处理了,例如strcpy等; -1:失败,需要强制转换(char*),再错误处理; |
头文件 | #include<shm.h> |
shmdt:解除映射
函数原型 | int shmdt ( char * shmaddr ); |
示例 | / |
参数1 | shmaddr,就是映射函数shmat的映射返回值 |
返回值 | 0:成功 -1:失败 |
头文件 | #include<shm.h> |
shmctl:关闭共享内存
函数原型 | int shmctl (int shmid, int cmd, struct shmid_t *buff ); |
示例 | / |
参数1 | shmid,对应的共享内存标识符; |
参数2 | cmd,设置为IPC_RMID; |
参数3 | 直接写NULL; |
返回值 | 0:成功 -1:失败 |
头文件 | #include<shm.h> |
7. 消息队列通信
(1)简介
①本质是一个消息的链表,具有特定的格式,消息被读走后,队列中将没有该条记录;
②声明周期是随内核持续,需要人工删除或者关机重启内核才行;
③目前广泛使用的是“系统V”型的消息队列;
④每个消息队列对应唯一的键值;
(2)函数实现
ftok:键值创建
函数原型 | int ftok (char* pathname, char proj ); |
功能 | 返回文件名对应的键值 |
参数1 | pathname,文件名 |
参数2 | proj,项目名(不为0即可) |
返回值 | 0:成功 -1:失败 |
头文件 | #include<sys/types.h> #include<sys/ipc.h> |
msgget:打开、创建消息队列(整体类似于shmget)
函数原型 | int msgget (key_t key, int msgflg ); |
功能 | 返回与键值对应的消息队列描述字,类似于文件描述符; |
参数1 | key,上述ftok返回的键值; |
参数2 | msgflg,标志位,类似于shmget中的shmflg,都是表示以什么样的方式打开对应的消息队列: ①IPC_CREAT:创建新的消息队列 ②IPC_EXCL:与IPC_CREAT一同使用,表示如果要创建的消息队列已经存在,则返回错误,用 ‘ | ’ 隔开; ③IPC_NOWAIT:读写消息队列要求无法满足时,不阻塞,不常用; |
返回值 | 成功:与键值对应的消息队列描述字,类似于文件描述符; -1:失败 |
头文件 | #include<sys/types.h> #include<sys/ipc.h> #include<sys/msg.h> |
msgsnd:发送消息
函数原型 | int msgsnd (int msqid, struct msgbuf* msgp, int msgsz, int msgflg ); |
功能 | 向消息队列中发送一条消息; |
参数1 | msqid,已打开的消息队列id; |
参数2 | msgp,存放消息的结构; struct msgbuf { long mtype; //消息类型 char mtext [1]; //消息数据的首地址 |
参数3 | msgsz,消息数据长度; |
参数4 | msgflg,发送标志,通常设置为 ①0:阻塞; ②IPC_NOWAIT,非阻塞; |
返回值 | 0:成功 -1:失败 |
头文件 | #include<sys/types.h> #include<sys/ipc.h> #include<sys/msg.h> |
msgrcv:接收消息
函数原型 | int msgrcv (int msqid, struct msgbuf* msgp, int msgsz, long msgtyp, int msgflg ); |
功能 | 从msqid代表的消息队列中读取一个msgtyp类型的消息,并把消息存储在msgp指向的msgbuf结构中。 且读取消息后,队列中的这条消息将自动被删除; |
参数1 | msqid,已打开的消息队列id; |
参数2 | msgp,存放消息的结构; struct msgbuf { long mtype; //消息类型 char mtext [ n ]; //接收消息数据的首地址,可根据具体情况改动; |
参数3 | msgsz,消息数据长度; |
参数4 | msgtyp,和发送消息中存放消息数据中的消息类型保持一致; |
参数5 | msgflg,发送标志,通常设置为 ①0:阻塞; ②IPC_NOWAIT,非阻塞; |
返回值 | 0:成功 -1:失败 |
头文件 | #include<sys/types.h> #include<sys/ipc.h> #include<sys/msg.h> |
8. 信号量
(1)信号量又称信号灯,主要用途是保护临界资源;进程可以根据它判定是否能够访问某些共享资源。处理用于访问控制,还可以用于进程同步;
(2)二值信号灯只能取0或者1,类似于互斥锁,但是两个不同:信号灯强调共享资源,只要共享资源可以用,其他进程同样可以修改信号灯的值;而互斥锁更强调进程,占用资源的进程使用完资源后,必须有进程本省来解锁;
(3)计数信号灯的值可以取任意非负值;