C语言-进程间通信_第1页
C语言-进程间通信_第2页
C语言-进程间通信_第3页
C语言-进程间通信_第4页
C语言-进程间通信_第5页
已阅读5页,还剩66页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、C语言-进程间通信上海*通信技术有限公司Mr Jim() 2014-04培训大纲n基础概念n进程产生的方式n进程间通信和同步nLinux下的线程同步同步与互斥与互斥的概念的概念临界资源与临界区n临界资源:一段时间内仅允许一个进程使用的资源称为临界资源。如:打印机、共享变量。n临界区:进程中访问临界资源的那段代码称为临界区,又称临界段。n同类临界区:所有与同一临界资源相关联的临界区。同步与互斥同步与互斥n同步:多个相互合作的进程在一些关键点上可能需要互相等待或互相交换信息,这种相互制约关系称为进程同步。n互斥:当一个进程正在使用某资源时,其他希望使用该资源的进程必须等待,当该进程用完资源并释放后

2、,才允许其他进程去访问此资源,我们称进程之间的这种相互制约关系为互斥。信号量说明:信号量就是操作系统中所用到的PV原语,广泛用于进程或线程间的同步与互斥。本质上是一个非负的整数计数器,被用来控制对公共资源的访问。nPV原语是对整数计数器信号量sem的操作。一次P操作使sem减一,而一次V操作使sem加一。进程或线程根据信号量的值来判断是否对公共资源具有访问权限n当信号量sem的值大于等于零时,该进程或线程具有公共资源的访问权限n相反,当信号量sem的值小于零时,该进程或线程就将阻塞直到信号量sem的值大于等于0为止nPV原语主要用于进程或线程间的同步和互斥两种典型情况。n如用于互斥,几个进程或

3、线程往往只设置一个信号量semn当信号量用于同步操作时,往往会设置多个信号量,并安排不同的初始值来实现他们之间的顺序执行nP操作和V操作是不可中断的程序段,称为原语P,V原语中P是荷兰语Passeren,相当于英文的pass,V是荷兰语的Verhoog,相当于increment。信号量信号量的定义信号量的定义n信号量由两个成员(s,q)组成,其中s是一个具有非负初值的整型变量,q是一个初始状态为空的队列。又称信号灯。n除信号量的初值外,信号量的值仅能由P操作(又称为wait操作)和V操作(又称为signal操作)改变。信号量的物理含义n信号量中的整型变量S表示系统中某类资源的数目。n当其值大于

4、0时,表示系统中当前可用资源的数目;n当其值小于0时,其绝对值表示系统中因请求该类资源而被阻塞的进程数目。P操作n设S为一个信号量,P(S)执行时主要完成下述动作:n SS1;nif(S 0) 设置进程状态为等待; 将进程放入信号量等待队列; 转调度程序;V操作nV(S)执行时主要完成下述动作:nSS1;nif(S0)将信号量等待队列中的第一个进程移出; 设置其状态为就绪状态并插入就绪队列; 然后再返回原进程继续执行;注意nP操作可能阻塞执行进程,而V操作可以唤醒其他进程。nP、V操作在封锁中断的情况下执行,即一个进程在信号量上操作时,不会有别的进程同时修改该信号量。也就是说P、V操作是原语。

5、培训大纲n程序、进程和线程的概念n进程产生的方式n进程间通信和同步nLinux下的线程进程号n每个进程在初始化的时候,系统都分配了一个ID号,用于标识此进程。在Linux中,进程号是唯一的,系统可以用这个值来表示一个进程,描述进程的ID号通常叫做PID,即进程ID(process id)。PID的变量类型是pid_t。ngetpid()函数介绍 #include #include pid_t getpid(); pid_t getppid();进程复制fork()n产生进程的方式比较多,fork()是其中的一种方式。fork()函数以父进程为蓝本复制一个进程,其ID号和父进程号不同。在Linu

6、x环境中,fork()是以写复制方式实现的,只有内存等与父进程不同,其它与父进程共享,只有在父进程或者子进程进行了修改后,才重新生成一份。nfork()函数介绍 fork()的原型如下,当成功时,fork()函数的返回值是进程的ID;失败则返回-1 #include #include pid_t fork(void); fork()的特点是执行一次,返回两次。父进程和子进程中返回的是不同的值。父进程返回的是子进程的ID号,而子进程返回的是0。system()方式nsystem()函数调用shell的外部命令在当前进程中开始另一个进程。nsystem()函数介绍 system()函数调用”/bi

7、n/sh-c command”执行特定的命令,阻塞当前进程直到command命令执行完毕。System()的原型如下: #include int system(const char * command); 执行system()函数时,会调用fork、execve、waitpid等函数,其中任意一个调用失败将导致system()函数调用失败。System()函数的返回值如下: 失败返回-1; 当sh不能执行时,返回127; 成功返回进程状态值; 进程执行exec 在使用fork()函数和system()函数的时候,系统中都会建立一个新的进程,执行调用者的操作,而原来的进程还会存在,直到用户显示的

8、退出; 而exec()函数与之前的fork()和system()函数不同,exec()函数会用新进程代替原有的进程,系统会从新的进程运行,新的进程的PID值会与原来进程的PID值相同 exec()函数,其原型如下: #include int execve(const char *path,char *const argv); 与fork()函数不同,exec()函数执行成功后不会返回,这是因为执行的新程序已经占用了当前进程的空间和资源,这些资源包括代码段、数据段和堆栈段等,它们都已经被新的内容取代,而进程的ID等标识性的信息仍然是原来的东西,即exec()函数在原来进程的壳上运行了自己的程序,

9、只有程序调用失败了,系统才会返回-1.所有用户态进程的产生进程initn在Linux系统中,所有的进程都是由父子或者堂兄关系的,没有哪个进程与其它进程完全独立。除了初始进程init,系统中每个进程都有一个父进程,新的进程不是被全新得创建,通常是从一个原有的进程进行复制或者克隆的。nLinux操作系统下的每一个进程都有一个父进程或者兄弟进程,并且有自己的子进程可以在Linux下使用命令pstree来查看系统中运行的进程之间的关系,如下所示可以看出init进程是所有进程的祖先,其他的进程都是由init进程直接或者间接fork()出来的。培训大纲n程序、进程和线程的概念n进程产生的方式n进程间通信和

10、同步nLinux下的线程进程间通信概述现在linux使用的进程间通信方式:n(1)无名管道(pipe)和有名管道(FIFO)n(2)消息队列n(3)共享内存n(4)信号量n(5)信号(signal)n(6)套接字(socket)进程间通信概述n进程间通信有如下一些目的:n数据传输:一个进程需要将它的数据发送给另一个进程。n共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到。n通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。n资源共享的同步:多个进程之间共享同样的资源。为了作到这一点,需要内核提供锁和同步

11、机制。n进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。管道n最早的一种进程间通信方式n半双工方式n只能在有共同祖先的进程之间使用,一般是父子进程n创建出管道后,调用fork产生新进程,在父子进程间使用nshell命令中的管道:ls | grep “music”管道通信管道通信例如:ps | grep vsftpd管道是单向的、先进先出的、无结构的字节流,它把一个进程的输出和另一个进程的输入连接在一起。写进程在管道的尾端写入数据,读进程在管道的首端读出数据。数据读出后将从管道中移走,其它读进程都

12、不能再读到这些数据。管道提供了简单的流控制机制。进程试图读空管道时,在有数据写入管道前,进程将一直阻塞。同样,管道已经满时,进程再试图写管道,在其它进程从管道中移走数据之前,写进程将一直阻塞。管道主要用于不同进程间通信。管道通信n管道有一些固有的局限性:n因为读数据的同时也将数据从管道移去,因此,管道不能用来对多个接收者广播数据。n管道中的数据被当作字节流,因此无法识别信息的边界。n如果一个管道有多个读进程,那么写进程不能发送数据到指定的读进程。同样,如果有多个写进程,那么没有办法判断是它们中那一个发送的数据。半双工管道n#include nint pipe(int fd2);n功能:创建一个

13、简单的管道,若成功则为数组fd分配两个文件描述符,其中fd0 用于读取管道,fd1 用于写入管道。n返回值:成功返回0,失败返回- 1。pipe调用成功后的状况半双工管道fd1fd0管道管道内核内核单个进程中的管道单个进程中的管道fd1fd0管道管道父子进程共享的管道父子进程共享的管道fd1fd0内核内核父进程父进程子进程子进程fd1管道管道fd0内核内核父进程父进程子进程子进程父子进程共享的管道父子进程共享的管道半双工管道n如果一个管道的写端口关闭了,读操作返回0n可以复制写描述符,让多个进程向同一个管道里面写n如果一个管道的读端口关闭了n写操作返回1,errno设置为EPIPEn产生SIG

14、PIPE信号n多个进程写一个管道,如果我们写的数据小于PIPE_BUF数据相互之间不会交叉;否则就可能交叉n利用管道同步进程 演示pipesyn命名管道(FIFO)n命名管道和一般的无名管道基本相同,但也有一些显著的不同:n命名管道是在文件系统中作为一个特殊的文件而存在的。n不同祖先的进程之间可以通过命名管道共享数据。n当共享管道的进程执行完所有的I / O操作以后,命名管道将继续保存在文件系统中以便以后使用。n无名管道只能由相关进程使用,它们共同的祖先进程创建了管道。但是,通过FIFO,不相关的进程也能交换数据。创建命名管道#include #include int mkfifo(const

15、 char * pathname, mode_t mode) ;n功能:创建命名管道n返回:若成功则为0,若出错则为- 1n建立管道后,就可以像普通文件一样读写操作n若不指定O_NONBLOCK只读方式打开时会阻塞直到有进程以写方式打开.同样写方式打开时会阻塞直到有进程以读方式打开命名管道(FIFO)n在shell的两个管道之间传送数据nmkfifo fifo1nprog3 fifo1 &nprog1 取队列上类型等于type的第一个消息n0 取队列上类型小于等于type绝对值的第一个消息消息队列nint msgctl(int msqid, int cmd, struct msqid_

16、des *buf)n正确返回0n错误返回-1n命令选项nIPC_STAT 取得当前状态nIPC_SET 设置msg_perm.uid, msg_perm.gid, msg_perm.mode, and msg_qbytes 能被有效用户ID是msg_perm.cuid 或msg_perm.uid或超级用户进程修改。长度只能被超级用户修改nIPC_RMID 移除消息队列和所有消息,只能被有效用户ID是msg_perm.cuid 或msg_perm.uid或超级用户进程执行信号量n信号量是由荷兰科学家Dijkstra提出的,是一种卓有成效的进程同步机制。n原理与进程同步一节讲的相同,但较复杂n不是

17、一个单独的值, 需要定义一个信号量集合n创建信号量与初始化是独立的过程, 无法原子完成n不会自动移除, 可能造成内存泄露n每个信号量在内核中都有一个semid_ds结构信号量int semget(key_t key, int nsems, int flag)n功能:创建一个新的信号量或取得一个新的信号量n返回值:成功返回信号量标志码,失败返回-1。n参数:key,整型值,其它进程访问信号量的依据。nsems,信号量个数,一般为1。semflg,标志,类似open()的标志,相当于文件的访问权限信号量nint semop(int semid, struct sembuf semoparray ,

18、size_t nops)n正确返回0, 错误返回-1n用于在信号量集合上执行一组操作,改变信号量的值n参数:semid,信号量标志码。sops,结构体指针,包含了具体操作。nsops,结构体数量,等于1。信号量struct sembufunsigned short sem_num; short sem_op; short sem_flg; ;n这三个字段的意义分别为:nsem_num:操作信号在信号集中的编号,第一个信号的编号是0。nsem_op:如果其值为正数,该值会加到现有的信号内值中。通常用于释放所控资源的使用权;如果sem_op的值为负数,而其绝对值又大于信号的现值,操作将会阻塞,直到

19、信号值大于或等于sem_op的绝对值。通常用于获取资源的使用权;如果sem_op的值为0,则操作将暂时阻塞,直到信号的值变为0。nsem_flg:信号操作标志,可能的选择有两种nIPC_NOWAIT /对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息。nIPC_UNDO /程序结束时(不论正常或不正常),保证信号值会被重设为semop()调用前的值。这样做的目的在于避免程序在异常情况下结束时未将锁定的资源解锁,造成该资源永远锁定。信号量int semctl(int semid, int semnum, int cmd /* union semun arg */)n功

20、能:控制信号量信息。n返回值:成功返回0,否则-1。n参数:semid,信号量标志码。semnum,信号量编号。cmd,要进行的操作。第四个参数,是union semun的实例,具体值依赖_cmd。信号量n命令个数比较多nIPC_STAT 取得信号量集合的semid_ds结构,存在arg.bug指向的区域nIPC_SET 设置sem_perm.uid, sem_perm.gid, and sem_perm.mode nIPC_RMID 从系统移除信号量集合nGETALL 取得所有信号量值, 存在arg.array指向区域nSETALL 设置所有信号量值, 从arg.array指向区域读取具有依

21、赖关系的作业调度问题具有依赖关系的作业调度问题n设P1、P2、P3、P4、P5、P6为一组合作进程,其前驱图如下右图所示。七个同步信号量a、b、c、d、e、f、g分别表示进程之间的前驱关系,如图所示,其初值均为0。这六个进程的同步描述如下: P1 P2 P6 P4 P3 P5abcdfeg解法(解法(1)P1() 执行P1的代码; v(a); v(b); P1 P2 P6 P4 P3 P5abcdfeg解法(解法(2)P2() p(a); 执行P2的代码; v(c); v(d); P1 P2 P6 P4 P3 P5abcdfeg生产者生产者消费者问题消费者问题n生产者消费者问题是最著名的进程同

22、步问题。n它描述了一组生产者进程向一组消费者进程提供产品,它们共享一个有界缓冲池。缓冲池中的每个缓冲区可以存放一个产品,生产者进程不断生产产品并将产品放入缓冲池中,消费者进程不断从缓冲池内取出产品并消费。生产者生产者消费者问题示意图消费者问题示意图n同步关系有:当缓冲池满时生产者进程需等待,当缓冲池空时消费者进程需等待。诸进程应互斥使用缓冲池。P1P2Pm有界缓冲池C1C2Ck共享内存n概述n一种最为高效的进程间通信的方式n进程可以直接读写内存。不需要任何数据的拷贝n内核专门留出一段内存区,使得多个进程间交换信息n此段内存区可以由需要访问的进程将其映射到自己的私有地址空间n共享内存的实现n实现

23、分为两个步骤n创建共享内存:用到的函数shmget,即从内存中获得一段共享内存区域n映射共享内存:把这段创建的共享内存映射到具体的进程空间,使用的函数shmatn还有撤销映射的操作,函数为shmdt共享内存n多个进程共享一块内存区域n速度快, 不需要复制内存n多个进程访问公共数据需要使用信号量来同步n内核为每个共享内存段维护一个结构共享内存进程一进程二共享内存共享内存原理示意图函数格式所需头文件#include #include #include 函数原型Int shmget(key_t key, int size, int shmflg)函数传入值Key:IPC_PRIVATEsize:共享

24、内存区大小Shmflg:同open函数的权限位,也可以用八进制表示法返回值成功:共享内存段标识符出错:-1Shmget函数语法要点共享内存void *shmat(int shmid, const void *addr, int flag)n把共享内存加载到进程空间n返回值:成功返回共享内存的虚拟地址起始地址,失败返回-1。如果addr是空, 加载到第一个可用的地址addr非空,并且没有指定SHM_RND标志, 加载到addr处addr非空,并且指定SHM_RND标志, 加载到地址(addr - (addr modulus SHMLBA)处共享内存n#includen int shmdt(con

25、st void *shmaddr);n功能:断连共享内存区的映射,把内存与进程分离, 但不从系统内移除n返回值:成功时返回0。失败时返回-1。 n参数:nshmaddr是共享内存的虚拟地址起始地址。共享内存n#includen int shmctl(int shmid, int cmd, struct shmid_ds *buf);n功能:共享内存控制函数。n返回值:成功返回0,失败返回-1。n参数:nshmid,共享内存的ID。ncmd,允许的操作,最常用的包括:IPC_RMID 删除共享内存段。 nbuf,保存内存模式状态和访问权限的数据结构,通常为0。n信号通信n信号概述n信号是UNIX

26、中使用的进程通信最古老的一种方法。n在软件层次上对中断机制的一种模拟,是一种异步通信方式n信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件n一个完整的信号生命周期可以分为3个重要阶段,这3个阶段由4个重要事件来刻画n信号产生n信号在进程中注册n信号在进程中注销n执行信号处理函数信号概述n产生信号:n当用户按某些终端键时,可以产生信号。例如:在终端上按Ctrl-C键通常产生中断信号(SIGINT)。这是停止一个已失去控制程序的方法。n 硬件异常产生信号:除数为0、无效的存储访问等等。这些条件通常由硬件检测到,并将其通知内核。然后内核为该条

27、件发生时正在运行的进程产生适当的信号。例如,对执行一个无效存储访问的进程产生一个SIGSEGV。n相邻两个事件的时间间隔构成信号生命周期的一个阶段n信号处理有多种方式,一般是由内核完成的,当然也可以有用户进程完成n不可靠信号的处理过程n发现该信号已经在进程中注册,则忽略该信号n故若前一个信号还未注销又产生了相同的信号就会产生信号丢失n可靠信号的处理过程n发现信号不管该信号是否已经在进程中注册,都会被再注册n所有可靠信号都支持排队,而不可靠信号则都不支持排队信号n可以要求系统在某个信号出现时按照下列三种方式中的一种进行操作。n(1) 忽略此信号。大多数信号都可使用这种方式进行处理,但有两种信号却

28、决不能被忽略。它们是:SIGKILL和SIGSTOP。这两种信号不能被忽略的原因是:它们向超级用户提供一种使进程终止或停止的可靠方法。另外,如果忽略某些由硬件异常产生的信号(例如非法存储访问),则进程的行为是未定义的。n(2) 捕捉信号。为了做到这一点要通知内核在某种信号发生时,调用一用户函数。在用户函数中,可执行用户希望对这种事件进行的处理。如果捕捉到SIGCHLD信号,则表示子进程已经终止,所以此信号的捕捉函数可以调用waitpid以取得该子进程的进程ID以及它的终止状态。 n(3) 执行系统默认动作。对大多数信号的系统默认动作是终止该进程。n下面是几个常见的信号。n1).SIGHUP:

29、从终端上发出的结束信号;n2).SIGINT: 来自键盘的中断信号(Ctrl-C);n3).SIGQUIT:来自键盘的退出信号(Ctrl-);n8).SIGFPE: 浮点异常信号(例如浮点运算溢出);n9).SIGKILL:该信号结束接收信号的进程;n11). SIGSEGV:对执行一个无效存储访问的进程产生一个SIGSEGVn14).SIGALRM:进程的定时器到期时,发送该信号;n15).SIGTERM:kill 命令发出的信号;n17).SIGCHLD:标识子进程停止或结束的信号;n19).SIGSTOP:来自键盘(Ctrl-Z)或调试程序的停止执行信号nn信号发送与捕捉nkill()和

30、raise()nkill函数同kill系统命令一样,可以发送信号给进程或进程组。n注意:它不仅可以中止进程,也可以向进程发送其他信号nraise()与kill不同的是,它允许进程向自身发送信号nalarm()和pause()nalarm也称为闹钟函数,它可以在进程中设置一个定时器,当定时器指定的时间到时,它就向进程发送SIGALARM信号。n注意:一个进程只能有一个闹钟时间,如果在调用alarm之前已设置过闹钟时间,则任何以前的闹钟时间都被新值所代替npause()函数是用于将调用进程挂起直至扑捉到信号为止。可用于判断信号是否已到n信号的处理n特定的信号是与一定的进程相联系的n一个进程可以决定

31、在该进程中需要对哪些信号进程什么样的处理信号处理函数#include int kill(pid_t pid, int sig);n 功能:发送信号到进程。n 返回值:成功返回0,否则返回-1。#include int alarm(unsigned int seconds);n功能: 在secends秒后发送SIGALARM信号。若secends为0,则取消已设置闹铃。n返回值:成功返回上一个闹铃还剩余多长时间,失败返回-1。n同时一个进程还可以选择如何处理信号n建立信号与进程之间的对应关系信号处理n简单的signal函数n使用信号集函数组nsignal()n只需把要处理的信号和处理函数列出即可

32、n主要用于前32种非实时信号的处理,不支持信号传递信息n信号集函数组n涉及一系列的函数,这些函数按照调用的先后次序分为以下几大功能模块n创建信号集合:创建用户感兴趣的信号n登记信号处理器:主要用于决定进程如何处理信号n检测信号:信号处理的后续步骤信号处理函数n#includensighandler_t signal(int sig, sighandler_t handler);n功能:信号处理函数。n返回值:成功返回可用信号处理函数,失败返回SIG_ERR。n参数:n sig,要捕获的信号。n handler,信号处理函数。还可以是以下取值:n SIG_IGN 屏蔽该信号n SIG_DFL 恢

33、复默认行为n 注:该函数简单但可靠性低,可由sigaction()代替。培训大纲n程序、进程和线程的概念n进程产生的方式n进程间通信和同步nLinux下的线程线程概述n进程是系统中程序执行和资源分配的单位。每个进程都拥有自己的数据段、代码段和堆栈段,故进程在进行切换等操作时需要进行大量上下文的切换工作n为进一步减少处理器的空转时间支持多处理器和减少上下文切换开销,进程演化出另一个概念线程n进程内的基本调度单位,也可称为轻量级进程n线程是在共享内存空间中并发的多道执行路径,共享一个进程的资源,如文件描述和信号处理n大大减少上下文切换的开销n一个进程可以有多个线程,既有多个线程控制表及堆栈寄存器,但却共享一个用户地址空间n线程基本操作n线程的创建和退出n函数说明:创建线程实际上就是确定调用该线程函数的入口点,通常使用pthread_createn线程创建之后,开始运行相关的线程函数,该函

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论