C语言-进程间通信上海****通信技术有限公司MrJim(seniordba@sina.com)2014-04培训大纲基础概念进程产生的方式进程间通信和同步Linux下的线程同步与互斥的概念临界资源与临界区临界资源:一段时间内仅允许一个进程使用的资源称为临界资源。如:打印机、共享变量。临界区:进程中访问临界资源的那段代码称为临界区,又称临界段。同类临界区:所有与同一临界资源相关联的临界区。同步与互斥同步:多个相互合作的进程在一些关键点上可能需要互相等待或互相交换信息,这种相互制约关系称为进程同步。互斥:当一个进程正在使用某资源时,其他希望使用该资源的进程必须等待,当该进程用完资源并释放后,才允许其他进程去访问此资源,我们称进程之间的这种相互制约关系为互斥。信号量说明:信号量就是操作系统中所用到的PV原语,广泛用于进程或线程间的同步与互斥。本质上是一个非负的整数计数器,被用来控制对公共资源的访问。PV原语是对整数计数器信号量sem的操作。一次P操作使sem减一,而一次V操作使sem加一。进程或线程根据信号量的值来判断是否对公共资源具有访问权限当信号量sem的值大于等于零时,该进程或线程具有公共资源的访问权限相反,当信号量sem的值小于零时,该进程或线程就将阻塞直到信号量sem的值大于等于0为止PV原语主要用于进程或线程间的同步和互斥两种典型情况。如用于互斥,几个进程或线程往往只设置一个信号量sem当信号量用于同步操作时,往往会设置多个信号量,并安排不同的初始值来实现他们之间的顺序执行P操作和V操作是不可中断的程序段,称为原语.P,V原语中P是荷兰语Passeren,相当于英文的pass,V是荷兰语的Verhoog,相当于increment。信号量信号量的定义信号量由两个成员(s,q)组成,其中s是一个具有非负初值的整型变量,q是一个初始状态为空的队列。又称信号灯。除信号量的初值外,信号量的值仅能由P操作(又称为wait操作)和V操作(又称为signal操作)改变。信号量的物理含义信号量中的整型变量S表示系统中某类资源的数目。当其值大于0时,表示系统中当前可用资源的数目;当其值小于0时,其绝对值表示系统中因请求该类资源而被阻塞的进程数目。P操作设S为一个信号量,P(S)执行时主要完成下述动作:S=S-1;if(S<0){设置进程状态为等待;将进程放入信号量等待队列;转调度程序;}V操作V(S)执行时主要完成下述动作:S=S+1;if(S≤0){将信号量等待队列中的第一个进程移出;设置其状态为就绪状态并插入就绪队列;然后再返回原进程继续执行;}注意P操作可能阻塞执行进程,而V操作可以唤醒其他进程。P、V操作在封锁中断的情况下执行,即一个进程在信号量上操作时,不会有别的进程同时修改该信号量。也就是说P、V操作是原语。培训大纲程序、进程和线程的概念进程产生的方式进程间通信和同步Linux下的线程进程号每个进程在初始化的时候,系统都分配了一个ID号,用于标识此进程。在Linux中,进程号是唯一的,系统可以用这个值来表示一个进程,描述进程的ID号通常叫做PID,即进程ID(processid)。PID的变量类型是pid_t。getpid()函数介绍#includesys/types.h#includeunistd.hpid_tgetpid();pid_tgetppid();进程复制fork()产生进程的方式比较多,fork()是其中的一种方式。fork()函数以父进程为蓝本复制一个进程,其ID号和父进程号不同。在Linux环境中,fork()是以写复制方式实现的,只有内存等与父进程不同,其它与父进程共享,只有在父进程或者子进程进行了修改后,才重新生成一份。fork()函数介绍fork()的原型如下,当成功时,fork()函数的返回值是进程的ID;失败则返回-1#includesys/types.h#includeunistd.hpid_tfork(void);fork()的特点是执行一次,返回两次。父进程和子进程中返回的是不同的值。父进程返回的是子进程的ID号,而子进程返回的是0。system()方式system()函数调用shell的外部命令在当前进程中开始另一个进程。system()函数介绍system()函数调用”/bin/sh-ccommand”执行特定的命令,阻塞当前进程直到command命令执行完毕。System()的原型如下:#includestdlib.hintsystem(constchar*command);执行system()函数时,会调用fork、execve、waitpid等函数,其中任意一个调用失败将导致system()函数调用失败。System()函数的返回值如下:失败返回-1;当sh不能执行时,返回127;成功返回进程状态值;进程执行exec在使用fork()函数和system()函数的时候,系统中都会建立一个新的进程,执行调用者的操作,而原来的进程还会存在,直到用户显示的退出;而exec()函数与之前的fork()和system()函数不同,exec()函数会用新进程代替原有的进程,系统会从新的进程运行,新的进程的PID值会与原来进程的PID值相同.exec()函数,其原型如下:#includeunistd.hintexecve(constchar*path,char*constargv[]);与fork()函数不同,exec()函数执行成功后不会返回,这是因为执行的新程序已经占用了当前进程的空间和资源,这些资源包括代码段、数据段和堆栈段等,它们都已经被新的内容取代,而进程的ID等标识性的信息仍然是原来的东西,即exec()函数在原来进程的壳上运行了自己的程序,只有程序调用失败了,系统才会返回-1.所有用户态进程的产生进程init在Linux系统中,所有的进程都是由父子或者堂兄关系的,没有哪个进程与其它进程完全独立。除了初始进程init,系统中每个进程都有一个父进程,新的进程不是被全新得创建,通常是从一个原有的进程进行复制或者克隆的。Linux操作系统下的每一个进程都有一个父进程或者兄弟进程,并且有自己的子进程.可以在Linux下使用命令pstree来查看系统中运行的进程之间的关系,如下所示.可以看出init进程是所有进程的祖先,其他的进程都是由init进程直接或者间接fork()出来的。培训大纲程序、进程和线程的概念进程产生的方式进程间通信和同步Linux下的线程进程间通信概述现在linux使用的进程间通信方式:(1)无名管道(pipe)和有名管道(FIFO)(2)消息队列(3)共享内存(4)信号量(5)信号(signal)(6)套接字(socket)进程间通信概述进程间通信有如下一些目的:数据传输:一个进程需要将它的数据发送给另一个进程。共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到。通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。资源共享的同步:多个进程之间共享同样的资源。为了作到这一点,需要内核提供锁和同步机制。进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。管道最早的一种进程间通信方式半双工方式只能在有共同祖先的进程之间使用,一般是父子进程创建出管道后,调用fork产生新进程,在父子进程间使用shell命令中的管道:ls|grep“music”管道通信管道通信例如:ps|grepvsftpd管道是单向的、先进先出的、无结构的字节流,它把一个进程的输出和另一个进程的输入连接在一起。写进程在管道的尾端写入数据,读进程在管道的首端读出数据。数据读出后将从管道中移走,其它读进程都不能再读到这些数据。管道提供了简单的流控制机制。进程试图读空管道时,在有数据写入管道前,进程将一直阻塞。同样,管道已经满时,进程再试图写管道,在其它进程从管道中移走数据之前,写进程将一直阻塞。管道主要用于不同进程间通信。管道通信管道有一些固有的局限性:因为读数据的同时也将数据从管道移去,因此,管道不能用来对多个接收者广播数据。管道中的数据被当作字节流,因此无法识别信息的边界。如果一个管道有多个读进程,那么写进程不能发送数据到指定的读进程。同样,如果有多个写进程,那么没有办法判断是它们中那一个发送的数据。半双工管道#includeunistd.hintpipe(intfd[2]);功能:创建一个简单的管道,若成功则为数组fd分配两个文件描述符,其中fd[0]用于读取管道,fd[1]用于写入管道。返回值:成功返回0,失败返回-1。pipe调用成功后的状况半双工管道fd[1]fd[0]管道内核单个进程中的管道fd[1]fd[0]管道父子进程共享的管道fd[1]fd[0]内核父进程子进程fd[1]管道fd[0]内核父进程子进程父子进程共享的管道半双工管道如果一个管道的写端口关闭了,读操作返回0可以复制写描述符,让多个进程向同一个管道里面写如果一个管道的读端口关闭了写操作返回1,errno设置为EPIPE产生SIGPIPE信号多个进程写一个管道,如果我们写的数据小于PIPE_BUF数据相互之间不会交叉;否则就可能交叉利用管道同步进程演示pipesyn命名管道(FIFO)命名管道和一般的无名管道基本相同,但也有一些显著的不同:命名管道是在文件系统中作为一个特殊的文件而存在的。不同祖先的进程之间可以通过命名管道共享数据。当共享管道的进程执行完所有的I/O操作以后,命名管道将继续保存在文件系统中以便以后使用。无名管道只能由相关进程使用,它们共同的祖先进程创建了管道。但是,通过FIFO,不相关的进程也能交换数据。创建命名管道#includesys/types.h#includesys/stat.hintmkfifo(constchar*pathname,mode_tmode);功能:创建命名管道返回:若成功则为0,若出错则为-1建立管道后,就可以像普通文件一样读写操作若不指定O_NONBLOCK只读方式打开时会阻塞直到有进程以写方式打开.同样写方式打开时会阻塞直到有进程以读方式打开命名管道(FIFO)在shell的两个管道之间传送数据mkfifofifo1prog3fifo1&prog1infile|teefifo1|prog2客户端和服务器间互相传数据IPC结构消息队列、信号量和共享内存有很多共同之处每一种结构都有一个内部的非负整数标识,从小到大分配外部使用key_t类型标识符每创建一个结构体就指定一个key_t类型标识符服务器使用IPC_PRIVATE分配一个结构体,把返回的标识符存在一个地方让客户端来了读取,或是父子进程服务器和客户端约定一个公共的标识服务器和客户端约定一个路径(已存在的文件)和(0-255)的数来生成一个标识key_tftok(constchar*path,intid);IPC结构使用get方得到一个IPC结构体如果key是IPC_PRIVATE就创建一个新结构体如果key没有与已存在的结构体关连,并且指定IPC_CREAT标志也会创建一个新的结构体要得到已有的结构体就不能指定IPC_CREAT或IPC_PRIVATE标志如果要创建一个新的结构体,同时指定IPC_CREAT和IPC_EXCL标志能防止引用一个已有的结构体;如果结构体已存在,就返回错误EEXISTIPC结构每个结构体都关连一个权限和用户结构体IPC结构每个结构体都是系统全局的,但是没有引用计数,不会被自动移除,容易因为编程不严格造成内存泄露无法通过lschmod等命令访问到增加命令ipcs和ipcr