POSIX(Portable Operating System Interface) 表示可移植操作系统接口。电气和电子工程师协会(Institute of Electrical and Electronics Engineers,IEEE)最初开发 POSIX 标准,是为了提高 UNIX 环境下应用程序的可移植性。然而,POSIX 并不局限于 UNIX,许多其它的操作系统,例如 DEC OpenVMS 和 Microsoft Windows,都支持 POSIX 标准。
为什么进程间需要通信?
(1)数据传输:一个进程需要将它的数据发送给另一个进程。
(2)资源共享:多个进程之间共享同样的资源。
(3)通知事件:一个进程需要向另一个或一组进程发送消息,通知它们发生了某种事件。
(4)进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有操作,并能够及时知道它的状态改变。
现在Linux使用的进程间通信方式包括:
(1)管道(pipe)和 有名管道(FIFO)
(2)信号(signal)
(3)消息队列
(4)共享内存
(5)信号量
(6)套接字(socket)
管道是单向的、先进先出的,它把一个进程的输出和另一个进程的输入连接在一起。一个进程(写进程)在管道的尾部写入数据,另一个进程(读进程)从管道的头部读出数据。
数据被一个进程读出后,将被从管道中删除,其它读进程将不能再读到这些数据。管道提供了简单的流控制机制,进程试图读空管道时,进程将阻塞。同样,管道已经满时,进程再试图向管道写入数据,进程将阻塞。
管道包括无名管道和有名管道两种,前者用于父、子进程间的通信,后者可用于运行于同一系统中的任意两个进程间的通信。
共享内存是被多个进程共享的一部分物理内存。共享内存是进程间共享数据的一种最快的方法,一个进程向共享内存区域写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容。
信号量(信号灯)与其他进程间通信方式不大相同,主要用途是保护临界资源。
进程可以根据它判定是否能够访问某些共享资源。除了用于访问控制外,还可用于进程同步。
(1)二值信号量:信号量的值只能取0或1,类似于互斥锁。 但两者有不同:
信号量强调共享资源,只要共享资源可用,其他进程同样可以修改信号量的值;
互斥锁更强调进程,占用资源的进程使用完资源后,必须由进程本身来解锁。
(2)计数信号量:信号灯的值可以取任意非负值。
信号能够传送的信息量有限,管道则只能传送无格式的字节流,消息队列(报文队列)则克服了这些缺点。
消息队列就是一个消息的链表。可以把消息看作一个记录,具有特定的格式。
目前主要有两种类型的消息队列:
(1)POSIX消息队列
(2)系统V消息队列:目前被大量使用;是随内核持续的,只有在内核重起或者人工删除时,该消息队列才会被删除。
消息队列的内核持续性要求每个消息队列都在系统范围内对应唯一的键值,所以,要获得一个消息队列的描述字,必须提供该消息队列的键值。
信号(signal)机制是Unix系统中最为古老的进程间通信机制,很多条件可以产生一个信号:
(1)当用户按某些按键时,产生信号。
(2)硬件异常产生信号:除数为0、无效的存储访问等等。这些情况通常由硬件检测到,将其通知内核,然后内核产生适当的信号通知进程,例如,内核对正访问一个无效存储区的进程产生一个SIGSEGV信号。
(3)进程用kill函数将信号发送给另一个进程。
(4)用户可用kill命令将信号发送给其他进程。
下面是几种常见的信号:
SIGHUP 从终端上发出的结束信号
SIGINT 来自键盘的中断信号(Ctrl-C)
SIGKILL 该信号结束接收信号的进程
SIGTERM kill 命令发出的信号
SIGCHLD 标识子进程停止或结束的信号
SIGSTOP 来自键盘(Ctrl-Z)或调试程序的停止执行信号
当某信号出现时,将按照下列三种方式中的一种进行处理:
(1)忽略此信号
大多数信号都按照这种方式进行处理,但SIGKILL和SIGSTOP不能被忽略:它们向超级用户提供了一种终止或停止进程的方法。
(2)执行用户希望的动作
通知内核在某种信号发生时,调用一个用户函数。在用户函数中,执行用户希望的处理。
(3)执行系统默认动作
对大多数信号的系统默认动作是终止该进程。
int pipe(int pipefd[2]); //创建无名管道
当一个管道建立时,它会创建两个文件描述符:pipefd[0] 读管道, pipefd[1] 写管道
关闭管道只需将这两个文件描述符关闭即可,可以使用普通的close函数逐个关闭。
通常先创建一个管道,再通过fork函数创建一个子进程,该子进程会继承父进程所创建的管道。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <sys/wait.h> int main() { int pipe_fd[2]; pid_t pid = -1; char r_buf[100] = {0}; char* str[2] = {"Hello,", " pipe!"}; int i, r_num, w_num, len; if(pipe(pipe_fd) < 0) { printf("pipe create error\n"); return -1; } pid = fork(); if(pid == 0) //child process { close(pipe_fd[1]); //close child process's write fd sleep(2); //wait parent process write data to its write fd if((r_num = read(pipe_fd[0], r_buf, 100))>0) { printf("child read success, r_num: %d, str: %s\n",r_num, r_buf); } close(pipe_fd[0]); exit(0); } else if(pid > 0) //parent process { close(pipe_fd[0]); //close parent process's read fd for(i = 0; i < 2; i++) { len = strlen(str[i]); w_num = write(pipe_fd[1], str[i], len); if( w_num == len ) printf("parent write success, r_num: %d, str: %s\n", len, str[i]); } close(pipe_fd[1]); waitpid(pid, NULL, 0); exit(0); } return 0; } root@ubuntu:/home/cindy/work/test# ./test parent write success, r_num: 6, str: Hello, parent write success, r_num: 6, str: pipe! child read success, r_num: 12, str: Hello, pipe! root@ubuntu:/home/cindy/work/test#
int mkfifo(const char *pathname, mode_t mode); //创建有名管道
pathname:FIFO文件名
mode:属性同open,一旦创建了一个FIFO,就可用open打开它,一般的文件访问函数(close、read、write等)都可用于FIFO
open 管道时,如果
无O_NONBLOCK:访问要求无法满足时进程将阻塞。如试图读取空的FIFO,将导致进程阻塞。
有O_NONBLOCK:访问要求无法满足时不阻塞,立刻出错返回,errno是ENXIO。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <errno.h> #include <fcntl.h> int main(int argc,char *argv[]) { int fd; char buf[20] = {0}; int len = strlen(argv[2]); if(argc != 3) { printf("usage: argv[1]: fifo name, argv[2]: message to send\n"); return -1; } //create fifo if((mkfifo(argv[1], O_CREAT|O_EXCL|O_RDWR) < 0) && (errno != EEXIST)) printf("cannot create pipe: %s\n", argv[1]); //open fifo fd = open(argv[1], O_RDWR|O_NONBLOCK, 0); if(fd == -1) { perror("open"); exit(1); } //write fifo if(write(fd, argv[2], len) != len) { if(errno == EAGAIN) printf("The FIFO has not been read yet. Please try later\n"); } if(read(fd, buf, len) == -1) { if(errno == EAGAIN) printf("no data yet\n"); } else printf("read from %s: %s\n", argv[1], buf); //close fifo close(fd); return 0; } root@ubuntu:/home/cindy/work/test# ./test fifo "my fifo" read from fifo: my fifo root@ubuntu:/home/cindy/work/test#
发送信号的主要函数:
int kill(pid_t pid, int sig); //kill(2)
int raise(int sig); //raise(3)
区别:
kill 向自身或其他进程发送信号
raise 向自身发送信号。
kill的pid参数有四种不同的情况:
(1)pid > 0 将信号发送给进程ID为pid的进程
(2)pid == 0 将信号发送给同组的进程
(3)pid < 0 将信号发送给其进程组ID等于pid绝对值的进程
(3)pid ==-1 将信号发送给所有进程
共享内存实现分为两个步骤:
(1)创建共享内存,使用shmget函数。
(2)映射共享内存,将这段创建的共享内存映射到具体的进程空间去,使用shmat函数
int shmget(key_t key, size_t size, int shmflg);
void *shmat(int shmid, const void *shmaddr, int shmflg);