LinuxC编程06_第1页
LinuxC编程06_第2页
LinuxC编程06_第3页
LinuxC编程06_第4页
LinuxC编程06_第5页
已阅读5页,还剩143页未读 继续免费阅读

下载本文档

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

文档简介

1、第第6 6章章LinuxLinux进程与进程调度进程与进程调度本章教学目的及要求 理解Linux的进程管理 了解ARM Linux进程控制相关API 掌握ARM Linux进程间通信API 了解Linux守护进程 6.1ARM Linux进程线程管理6.1.1进程描述符及任务结构进程描述符及任务结构 1进程进程进程是一个实体。每一个进程都有它自己的地址空间,包括文本区域、数据区域和堆栈区域。进程是一个“执行中的程序” 进程是由进程控制块、程序段、数据段组成 。一个进程可以包含若干线程(Thread)进程可以划分为运行、阻塞、就绪三种状态,并随一定条件而相互转化:就绪-运行,运行-阻塞,阻塞-就

2、绪。 2. 进程描述符Linux系统中包括下面几种类型的进程。交互进程:由shell启动的进程。既可在前台运行,也可在后台运行。处理进程:这种进程和终端没有联系,它被提交到一个队列中的进程序列。守护进程:又称监控进程,是Linux系统启动时开始执行的进程,在后台运行。进程控制块(进程控制块(PCBPCB) shruct task_struct unsigned long state; /进程的状态 unsigned long policy; /描述进程调度策略,判断是实时进程还是非实时进程 struct task_struct *parent; /组织进程的层次关系,指向父进程 struct

3、list_head tasks; /通过list_head组织成双向链表 pid_t pid; /每个进程唯一的标号 . ;(1)进程状态(State) Linux中的进程主要有如下状态: TASK_RUNNING:正在运行或在就绪队列run-queue中准备运行的进程,实际参与进程调度。 TASK_INTERRUPTIBLE:处于等待队列中的进程,待资源有效时唤醒,也可由其它进程通过信号(signal)或定时中断唤醒后进入就绪队列run-queue。 TASK_UNINTERRUPTIBLE:处于等待队列中的进程,待资源有效时唤醒,不可由其它进程通过信号(signal)或定时中断唤醒。 TA

4、SK_ZOMBIE:表示进程结束但尚未消亡的一种状态(僵死状态)。此时,进程已经结束运行且释放大部分资源,但尚未释放进程控制块。 TASK_STOPPED:进程被暂停,通过其它进程的信号才能唤醒。导致这种状态的原因有二,或者是对收到SIGSTOP、SIGSTP、SIGTTIN或SIGTTOU信号的反应,或者是受其它进程的ptrace系统调用的控制而暂时将CPU交给控制进程。(2)进程调度信息 need_resched:在“下一次的调度机会”就调用调度程序schedule()。 counter:进程剩余的时间片,是进程调度的主要依据,也可以说是进程的动态优先级,因为这个值在不断地减少。 nice

5、:进程的静态优先级,同时也代表进程的时间片,用于对counter赋值,可以用nice()系统调用改变这个值。 policy:进程的调度策略,实时进程和普通进程的调度策略是不同的。 rt_priority:只对实时进程有意义。进程的调度策略进程的调度策略 三种进程的调度策略: l. SCHED_OTHER:其他调度 2. SCHED_FIFO:先来先服务调度 3. SCHED_RR:时间片轮转调度(3)标识符(Identifiers)Pid:进程标识符Uid、gid:用户标识符、组标识符Euid、egid:有效用户标识符、有效组标识符Suid、sgid:备份用户标识符、备份组标识符Fsuid、f

6、sgid:文件系统用户标识符、文件系统组标识符(4)进程通信有关信息(IPC)Spinlock_t sigmask_lock:信号掩码的自旋锁Long blocked:信号掩码Struct signal *sig:信号处理函数Struct sem_undo *semundo:为避免死锁而在信号量上设置的取消操作Struct sem_queue *semsleeping:与信号量操作相关的等待队列(5)进程链接信息(Links)p_opptr(Original parent):祖先p_pptr(Parent):父进程p_cptr(Child):子进程p_ysptr(Younger sibling

7、):弟进程p_osptr(Older sibling):兄进程Pidhash_next、Pidhash_pprev:进程在哈希表中的链接Next_task、 prev_task:进程在双向循环链表中链接Run_list:运行队列的链表(6)文件系统信息(File System)Sruct fs_struct *fs: 进程的可执行映象所在的文件系统Struct files_struct *files: 进程打开的文件(7)虚拟内存信息(Virtual Memory) 除了内核线程(kernel thread),每个进程都拥有自己的地址空间(也叫虚拟空间),用mm_struct来描述。另外Lin

8、ux2.4还引入了另外一个域active_mm,这是为内核线程而引入。为了让内核线程与普通进程具有统一的上下文切换方式,当内核线程进行上下文切换时,让切换进来的线程的active_mm 指向刚被调度出去的进程的active_mm(如果进程的mm域不为空,则其active_mm域与mm域相同)。虚拟内存描述信息Struct mm_struct *mm:描述进程的地址空间Struct mm_struct *active_mm:内核线程所借用的地址空间6.1.2进程的调度调度程序运行时,要在所有处于可运行状态的进程之中选择最值得运行的进程投入运行。选择进程的依据是什么呢?在每个进程的task_str

9、uct 结构中有这么四项:policy,priority,counter,rt_priority这四项就是调度程序选择进程的依据.其中,policy是进程的调度策略,用来区分两种进程-实时和普通;priority是进程(实时和普通)的优先级;counter 是进程剩余的时间片,它的大小完全由priority决定;rt_priority是实时优先级,这是实时进程所特有的,用于实时进程间的选择。进程的调度策略有以下几种(由进程的调度策略有以下几种(由policypolicy的值决定:的值决定:1SCHED_OTHER:分时调度当所有任务都采用linux分时调度策略时: (1)创建任务指定采用分时调

10、度策略,并指定优先级nice值(-2019)。 (2)将根据每个任务的nice值确定在CPU上的执行时间(counter)。 (3)如果没有等待资源,则将该任务加入到就绪队列中。(4)调度程序遍历就绪队列中的任务,通过对每个任务动态优先级的计算(counter+20-nice)结果,选择计算结果最大的一个去运行,当这个时间片用完后(counter减至0)或者主动放弃CPU时,该任务将被放在就绪队列末尾(时间片用完)或等待队列(因等待资源而放弃CPU)中。(5)此时调度程序重复上面计算过程,转第4步。(6)当调度程序发现所有就绪任务计算所得的权值都为不大于0时,重复第2步。2SCHED_FIFO

11、:实时调度(先到先服务)当所有任务都采用FIFO时:(1)创建进程时指定采用FIFO,并设置实时优先级rt_priority(1-99)。(2)如果没有等待资源,则将该任务加入到就绪队列中。(3)调度程序遍历就绪队列,根据实时优先级计算调度权值(1000+rt_priority),选择权值最高的任务使用cpu。(4)调度程序发现有优先级更高的任务到达(高优先级任务可能被中断或定时器任务唤醒,再或被当前运行的任务唤醒,等等),则调度程序立即在当前任务堆栈中保存当前cpu寄存器的所有数据,重新从高优先级任务的堆栈中加载寄存器数据到cpu,此时高优先级的任务开始运行。重复第3步。(5)如果当前任务因

12、等待资源而主动放弃cpu使用权,则该任务将从就绪队列中删除,加入等待队列,此时重复第3步。3SCHED_RR:时间片轮转调度当所有任务都采用RR调度策略时:(1)创建任务时指定调度参数为RR,并设置任务的实时优先级和nice值。(2)若无等待资源,则将该任务加入到就绪队列中。(3)调度程序遍历就绪队列,根据实时优先级计算调度权值(1000+rt_priority),选择权值最高的任务使用CPU。(4)如果就绪队列中的RR任务时间片为0,则会根据nice值设置该任务的时间片,同时将该任务放入就绪队列的末尾。重复步骤3。(5)当前任务由于等待资源而主动退出CPU,则其加入等待队列中。重复步骤3。4

13、系统中既有分时调度,又有时间片轮转调度和先进先出调度(1)RR调度和FIFO调度的进程是实时进程,分时调度的进程是非实时进程。(2)当实时进程准备就绪后,如果当前CPU正在运行非实时进程,则实时进程立即抢占非实时进程。(3)RR进程和FIFO进程都采用实时优先级做为调度的权值标准,RR是FIFO的一个延伸。FIFO时,如果两个进程的优先级一样,则这两个优先级一样的进程具体执行哪一个是由其在队列中的未知决定的,这样导致一些不公正性(优先级是一样的,为什么要让你一直运行?),如果将两个优先级一样的任务的调度策略都设为RR,则保证了这两个任务可以循环执行,保证了公平。 6.2ARM Linux进程控

14、制相关API 1. 进程的创建(1)头文件: #include /提供类型pid_t的定义 #include (2)函数原型: pid_t fork( void );(3)函数的返回值: 子进程中返回0,父进程中返回子进程ID,出错返回-1。(4)函数说明: 一个现有进程可以调用fork函数创建一个新进程。由fork创建的新进程被称为子进程(child process)。fork函数被调用一次但返回两次。两次返回的唯一区别是子进程中返回0值,而父进程中返回子进程ID。例 6.1 下面程序是创建一个子进程#include #include main() pid_t pid; pid = fork

15、(); if(!pid) printf(this is child ); else if (pid0) printf(this is parent,child has pid %d ,pid); else printf(fork fail ); 例 6.2创建一个子进程的实例#include /* 提供类型fork()和pid_t的定义 */#include /* 提供 wait() 的系统调用 */main() pid_t child_pid; int child_status; child_pid = fork(); /* 创建一个子进程 */switch (child_pid) /* 检

16、查fork() 创建子进程 */ case -1: /* fork() 创建子进程失败 */ perror(fork); /* 输出系统定义的错误信息 */ exit(1); case 0: /* fork()创建子进程成功 */ printf(hello worldn); exit(0); /* 子进程中返回 */ default: /* fork()创建成功, 父进程中返回 */ wait(&child_status); /* 等待,直到子进程退出 */ 2进程的执行(1)函数原型 在Linux中,exec指的是一组函数,共有6种调用形式,它们的声明格式如下:int execl(c

17、onst char *path, const char *arg, .);int execlp(const char *file, const char *arg, .);int execle(const char *path, const char *arg, ., char *const envp );int execv(const char *path, char *const argv );int execvp(const char *file, char *const argv );int execve(const char *path, char *const argv , cha

18、r *const envp );(2)头文件及外部变量#include extern char *environ;(3)函数返回值函数调用出错时返回-1;函数调用成功不返回值。(4)函数说明 在以上6个exec函数中,第1个参数如果为pathname,则说明被执行程序是由路径名指定的,如果为filename,则说明是由文件名指定的;第2个参数如果为数组,说明被执行程序的参数是由一个数组来索引(数组必须含有一个空指针来表示结束),否则需要将参数一一列出。还有2个以p结尾的函数execlp和execvp,除execlp和execvp之外的4个函数都要求,第1个参数path必须是一个完整的路径,如“

19、/bin/ls”;而execlp和execvp的第1个参数file可以简单到仅仅是一个文件名,如“ls”,这两个函数可以自动到环境变量PATH制定的目录里去寻找。exec函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件。这里的可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。例 6.3 下面是一个fork()结合exec()的程序实例#include #include main() int pid; pid = fork(); switch(pid) case -1: perror(fork fail

20、ed); exit(1); case 0: execl(/bin/ls,ls,-l,NULL); perror(execl failed); exit(1); 下面来通过几个小程序来区别一下这几个函数的使用。(1)函数execl的使用示例:#includemain()int ret;ret = execl(“/bin/ls”,”ls”,”-al”,”/etc/passwd”,(char * )0);说明:使用了execl函数时需要给出完整的文件目录来查找对应的可执行文件。本例是通execl函数来执行/bin/ls -al /etc/passwd命令,结果如下:-rw-r-r- 1 root r

21、oot 705 Sep 3 13 :52 /etc/passwd(2)execlp函数的使用示例#includemain() int ret; ret = execlp(“ls”,”ls”,”-al”,”/etc/passwd”,(char *)0);说明:execlp()会从PATH 环境变量所指的目录中查找符合参数file的文件名,找到后便执行该文件,然后将第二个以后的参数当做该文件的argv0、argv1,最后一个参数必须用空指针(NULL)作结束。返回值:如果执行成功则函数无返回值,执行失败则直接返回-1,失败原因存于errno中。本例程序通过函数来执行ls -al /etc/pass

22、wd命令,结果如下:-rw-r-r- 1 root root 705 Sep 3 13 :52 /etc/passwd(3)execv 函数的使用示例#includemain() char * argv =“ls”,”-al”,”/etc/passwd”,(char*) ; execv(“/bin/ls”,argv); 说明:execv()用来执行参数path字符串所代表的文件路径,与execl()不同的地方在于execve()只需两个参数,第二个参数利用数组指针来传递给执行文件。返回值:成功则函数无返回值,失败则直接返回-1。本程序是通过execv函数来执行/bin/ls -al /etc/

23、passwd命令,结果如下:-rw-r-r- 1 root root 705 Sep 3 13 :52 /etc/passwd(4)execve 函数的使用示例(execve.c)#include #include #include int main( void ) char* args = /bin/ls, NULL ;if ( -1 = (execve(/bin/ls, args, NULL) ) perror( execve ); exit( EXIT_FAILURE); puts( shouldnt get here ); exit( EXIT_SUCCESS );3进程的消亡通常通过

24、系统调用exit 来终止发出调用的进程。其函数原型和参数说明如下:(1)exit和_exit函数语法 头文件#include /* Exit的函数声明在stdlib.h头文件中 */#include /* _exit的函数声明在unistd.h头文件当中 */ 函数原型void exit(int status); void _exit(int status); 函数说明exit和_exit函数都是用来终止进程的,一般情况下,0表示正常结束;其他的数值表示出现了错误,进程非正常结束。当程序执行到exit或_exit时,系统无条件的停止剩下所有操作,清除包括PCB在内的各种数据结构,并终止本进程的

25、运行。但是,这两个函数是有区别的。exit()函数的作用是:直接使用进程停止运行,清除其使用的内存空间,并清除其在内核中的各种数据结构;exit()函数则在这一基础上做了一些包装。在执行退出之前加了若干道工序。exit()函数与_exit()函数最大区别就在于exit()函数在调用exit系统之前要检查文件的打开情况,把文件缓冲区的内容写回文件。/下面是用exit()函数结束进程的程序代码:#include #include int main(void) printf(Using exit.n); printf(This is the content in buffer); exit(0);输

26、出信息:Using exit.This is the content in buffer4wait和waitpid系统调用 当进程终止时,会向其父进程发送SIGCHLD信号, 这个异步事件可以在父进程运行的任何时候发生,包括正常和异常终止两种。调用wait和waitpid的进程可能会有以下3种情况:(1)阻塞:如果其所有的子进程都还在运行 (2)带子进程的终止状态正常返回:其中一个 子进程终止(3)出错返回:没有子进程(1)wait 函数(等待子进程中断或结束)v 头文件v#include v#include v 函数原型vpid_t wait(int *status) /*v 函数说明vwa

27、it()暂停目前进程的执行,直到有信号来到或子进程结束。如果在调用 wait()时子进程已经结束。v 函数返回值v如果执行成功则返回子进程识别码(PID),如果有错误发生则返回,返回值-1。失败原因存于 errno 中。(2)waitpid 函数(等待子进程中断或结束) 头文件 #include #include 函数原型 pid_t waitpid( pid_t pid, /* 等待结束的进程类型 */ int * status, /* 表示子进程退出时的状态 */ int options ); /* 选项 */ 函数说明 调用waitpid()与调用wait()的区别是waitpid等待由

28、参数pid指定的子进程退出。 参数pid 的含义:pid0时只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。pid=-1时等待任何子进程退出,相当于调用 wait()。调用waitpid中的参数options的含义WNOHANG:如果没有任何已经结束的子进程则马上返回, 不予以等待。WUNTRACED:如果子进程进入暂停执行情况则立即返回,但结束状态不予以理会。子进程的结束状态返回后存于 status。 函数返回值waitpid的返回值共有3种情况: 当正常返回的时候,返回收集到的子进程的进程ID; 如果设置了

29、选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0; 出错,则返回-1,errno被设置成相应的值。例6.4 wait函数应用举例1/* wait1.c */#include #include #include #include main()pid_t pc,pr;pc=fork();if(pc0) /* 如果出错 */printf(error ocurred!n);else if(pc=0)/* 如果是子进程 */ printf(This is child process with pid of %dn,getpid(); sleep(10); /* 睡眠10秒

30、钟 */ else/* 如果是父进程 */ pr=wait(NULL);/* 在这里等待 */ printf(I catched a child process with pid of %dn),pr); exit(0);编译并运行:$ gcc wait1.c -o wait1$ ./wait1This is child process with pid of 1508I catched a child process with pid of 1508显然,在第2行结果打印出来前有10秒钟的等待时间,这就是我们设定的让子进程睡眠的时间,只有子进程从睡眠中苏醒过来,它才能正常退出,也就才能被父进

31、程捕捉到。其实这里我们不管设定子进程睡眠的时间有多长,父进程都会一直等待下去,读者如果有兴趣的话,可以试着自己修改一下这个数值,看看会出现怎样的结果。例6.5 wait函数应用举例2/* wait2.c */#include #include #include main() int status;pid_t pc,pr;pc=fork();if(pc0)/* 如果出错 */printf(error ocurred!n);else if(pc=0)/* 子进程 */ printf(This is child process with pid of %d.n,getpid();exit(3);/*

32、 子进程返回3 */else/* 父进程 */pr=wait(&status);if(WIFEXITED(status)/* 如果WIFEXITED返回非零值 */printf(the child process %d exit normally.n,pr);printf(the return code is %d.n,WEXITSTATUS(status);else /* 如果WIFEXITED返回零 */printf(the child process %d exit abnormally.n,pr); 编译并运行:$ gcc wait2.c -o wait2$ ./wait2Th

33、is is child process with pid of 1538.the child process 1538 exit normally.the return code is 3.v父进程捕捉到子进程的返回值3,并把它打印了出来。v例中的wait函数调用,用WIFEXITED(status)宏是来判断子进程是否为正常退出的,如果是,它会返回一个非零值。v注意:这里的参数status并不同于wait(int *status)中的status参数(指向整数的指针status)。例例6 6.6 .6 进程同步实例 有时父进程要求子进程的运算结果进行下一步的运算,或者子进程的功能是为父进程提

34、供了下一步执行的先决条件(如:子进程建立文件,而父进程写入数据),此时父进程就必须在某一个位置停下来,等待子进程运行结束,而如果父进程不等待而直接执行下去的话,就会出现极大的混乱。本例用wait系统调用解决进程同步。程序代码如下:#include #include main()pid_t pc, pr;int status;pc=fork();if(pc0)printf(Error occured on forking.n);else if(pc=0)/* 子进程的工作 */exit(0);else/* 父进程的工作 */pr=wait(&status); /* 利用子进程的结果 */

35、例6.7 waitpid函数的应用举例/* waitpid.c */#include #include #include main()pid_t pc, pr;pc=fork(); if(pc0)/* 如果fork出错 */ printf(Error occured on forking.n);else if(pc=0)/* 如果是子进程 */sleep(10);/* 睡眠10秒 */exit(0); /* 如果是父进程 */dopr=waitpid(pc, NULL, WNOHANG);/* 使用了WNOHANG参数,waitpid不会在这里等待 */if(pr=0) /* 如果没有收集到子

36、进程 */printf(No child exitedn);sleep(1); while(pr=0); /* 没有收集到子进程,就回去继续尝试 */if(pr=pc)printf(successfully get child %dn, pr);elseprintf(some error occuredn); 编译并运行:$ gcc waitpid.c -o waitpid$ ./waitpidNo child exitedNo child exitedNo child exitedNo child exitedNo child exitedNo child exitedNo child ex

37、itedNo child exitedNo child exitedNo child exitedsuccessfully get child 1526父进程经过10次失败尝试之后,终于收集到了退出的子进程。6.3ARM Linux进程间通信API 6.3.1Linux中进程间通信最初Unix IPC包括:管道(pipe)、命名管道(FIFO)、信号(signal)。System V IPC包括:System V消息队列(message queue)、System V信号灯(semaphore)、System V共享内存区(shared memory)。Posix IPC包括:Posix消息队

38、列(message queue)、Posix信号灯(semaphore)、Posix共享内存区(shared memory)、套接字(Socket)。Linux下进程间通信的几种主要手段: 1管道(Pipe)及有名管道(named pipe) 管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除有管道所具有的功能外,它还允许无亲缘关系进程间的通信。2信号(Signal) 信号是用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;Linux除了支持Unix早期信号语义函数signal外,还支持语义符合Posix.1标准的信号函数signa

39、ction(实际上,该函数是基于 BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用signaction函数重新实现了signal函数)。 3报文(Message)队列(消息队列) 消息队列是消息的链接表,包括Posix消息队列systemV消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。4共享内存 共享内存是运行在同一台机器上的进程间通信最快的方式,因为数据不需要在不同的进程间复制,使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。通常由一个进程创建一块共享内存区,其余进程对这块内存区进行读写。是针对其他通信机制运行效率较低而设计

40、的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。5信号量(semaphore)又称为信号灯,用来协调不同进程间的数据对象的,而最主要的应用是前一节的共享内存方式的进程间通信。本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。一般说来,为了获得共享资源,进程需要执行下列操作:(1) 测试控制该资源的信号量。(2) 若此信号量的值为正,则允许进行使用该资源。进程将信号量减1。(3) 若此信号量为0,则该资源目前不可用,进程进入睡眠状态,直至信号量值大于0,进程被唤醒,转入步骤(1)。(4) 当进程不再使用一个信号量控制的资源时,信号量值加1。如果此时有进

41、程正在睡眠等待此信号量,则唤醒此进程。6.3.2管道1管道的创建和读写(1)管道的创建头文件:#include 命令格式:int pipe(int 管道名2)返回值:正确返回:0;错误返回:-1。参数说明:管道名1为写入端,管道名0为读出端。功能:创建一个管道名为指定名称的无名管道,以便于创建管道的进程及其子孙进程共享。例如,当定义了int pipe(int fp2);则fp1用于写,fp0用于读。(2)管道的读写写管道 write(管道名1,buf,size);读管道 read(管道名0,buf,size);参数说明:buf为程序中定义的字符型数组或缓冲区;size为读写的信息长度。注意:管

42、道为一临界资源,因此父子进程之间除了需要读写同步以外,在对管道进行读写操作时还需要互斥进入。如果进程需要实现互斥,因为管道是文件,可以使用下述对文件上锁和开锁的系统调用:lockf(files,function,size);参数说明:files是需加以封锁的文件描述符,此处可是管道的读写端口; function是功能选择,为1表示上锁,为0表示开锁;size表示锁定或开锁的字节数,其值为0则表示文件全部内容。2管道应用举例例6.8 编写一个程序,实现以下功能。(1)父进程使用系统调用pipe()建立一个管道;(2)创建两个子进程,分别向管道各发下面中一条信息后结束: This is the f

43、irst message! This is the second message!(3)父进程从管道中分别接收两个子进程发来的消息并显示该消息,然后父进程结束。两个子进程的发送没有先后要求。源程序:#include#include#includemain() int p1,p2,fd2; char outpipe50; char inpipe150= This is the first message!; char inpipe250= This is the second message!; pipe(fd); while(p1=fork()=-1); if (p1=0) lockf(fd1

44、,1,0); write(fd1,inpipe1,50); exit(0); else while(p2=fork()=-1); if (p2=0) lockf(fd1,1,0); write(fd1,inpipe2,50); exit(0); else wait(0); read(fd0,outpipe,50); lockf(fd1,0,0); printf(Parent has received first message:n); printf(%sn,outpipe); wait(0);/不在意结束状态值 read(fd0,outpipe,50); lockf(fd1,0,0); pri

45、ntf(Parent has received second message:n); printf(%sn,outpipe); exit(0); 本程序通过父进程使用系统调用pipe()新建一个无名管道,然后父进程创建两个子进程,之后父进程就一直等待子进程发送完信息。两个子进程通过父进程创建的管道,发送已经定义好的消息inpipe1和inpipe2然后退出。父进程则分别接收两个消息,先接收完第一个子进程的消息并显示,紧接着继续接收下一个子进程发来的消息并显示,然后退出程序。例6.9 先创建一个管道,而后创建一个子进程。让子进程向管道里写数据,让父进程从管道读数据,程序在写和读之前都把不用的描述

46、符关掉。#include #include #include #include int main()int n;int fd2;pid_t pid;char line1024;if(pipe(fd)0) /*建立管道*/ perror(“pipe error”);if(pid=fork()0) /*创建子进程*/ perror(“fork error”);else if(pid=0) /*若是子进父程*/ close(fd0); /*关闭读描述符*/ write(fd1,”Im father pid,hello child!”,23); /*往管道里写数据*/else /*如果是父进程*/ c

47、lose(fd1); /*关闭*/ wait(); /*等待子进程结束*/ n=read(fd0,line,1024); /*从管道里读数据,读到缓冲数组中*/ write(STDOUT_FILENO ,line,n); /*把缓冲区数据写道屏幕上*/ exit(0);程序的运行结果是 Im father pid, hello child!Linux用函数pipe来创建管道,经由参数filedes返回两个文件描述符:filedes0为读而打开,filedes1为写而打开。filedes1的输出是filedes0的输入。6.3.3 命名管道(FIFO)1有名管道的创建#include #incl

48、ude int mkfifo(const char * pathname, mode_t mode); 函数的第一个参数是一个普通的路径名,即是创建后FIFO的名字。第二个参数与打开普通文件的open()函数中的mode 参数相同。若mkfifo的第一个参数是一个已经存在的路径名时,返回EEXIST错误,如果确实返回该错误,则只要调用打开FIFO的函数。一般文件的I/O函数都可用于FIFO,如close、read、write等。2. 有名管道的打开规则有名管道比管道多了一个打开操作:open。FIFO的打开规则:v如果当前打开操作是为读而打开FIFO时,若已经有相应进程为写而打开该FIFO,则

49、当前打开操作将成功返回;否则,可能阻塞直到有相应进程为写而打开该FIFO(当前打开操作设置了阻塞标志);或者,成功返回(当前打开操作没有设置阻塞标志)。v如果当前打开操作是为写而打开FIFO时,如果已经有相应进程为读而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为读而打开该FIFO(当前打开操作设置了阻塞标志);或者,返回ENXIO错误(当前打开操作没有设置阻塞标志)。3. 有名管道的读写规则有名管道的读写规则(1)从FIFO中读取数据 如果一个进程为了从FIFO中读取数据而阻塞打开FIFO,那么称该进程内的读操作为设置了阻塞标志的读操作。 如果有进程写打开FIFO,

50、且当前FIFO内 没有数据,则对于设置了阻塞标志的读操作来说,将一直阻塞。对于没有设置阻塞标志读操作来说则返回-1,当前errno值为EAGAIN,提醒以后再试。 对于设置了阻塞标志的读操作说,造成阻塞的原因有两种:当前FIFO内有数据,但有其它进程在读这些数据;另外就是FIFO内没有数据。解阻塞的原因则是FIFO中有新的数据写入,不论写入数据量的大小,也不论读操作请求多少数据量。 读打开的阻塞标志只对本进程第一个读操作施加作用,如果本进程内有多个读操作序列,则在第一个读操作被唤醒并完成读操作后,其它将要执行的读操作将不再阻塞,即使在执行读操作时,FIFO中没有数据也一样(此时,读操作返回0)

51、。 如果没有进程写打开FIFO,则设置了阻塞标志的读操作会阻塞.注意:如果FIFO中有数据,则设置了阻塞标志的读操作不会因为FIFO中的字节数小于请求读的字节数而阻塞,此时,读操作会返回FIFO中现有的数据量。(2) 向FIFO中写入数据如果一个进程为了向FIFO中写入数据而阻塞打开FIFO,那么称该进程内的写操作为设置了阻塞标志的写操作。(3)对于设置了阻塞标志的写操作当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果此时管道空闲缓冲区不足以容纳要写入的字节数,则进入睡眠,直到当缓冲区中能够容纳要写入的字节数时,才开始进行一次性写操作。 当要写入的数据量大于PIPE

52、_BUF时,linux将不再保证写入的原子性。FIFO缓冲区一有空闲区域,写进程就会试图向管道写入数据,写操作在写完所有请求写的数据后返回。 (4)对于没有设置阻塞标志的写操作当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。在写满所有FIFO空闲缓冲区后,写操作返回。 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果当前FIFO空闲缓冲区能够容纳请求写入的字节数,写完后成功返回;如果当前FIFO空闲缓冲区不能够容纳请求写入的字节数,则返回EAGAIN错误,提醒以后再写;4命名管道应用举例例6.10 本例使用FIFO进行进程间通信,程序luc

53、y.c 创建了FIFO write_fifo用于向程序peter.c 发送消息;程序创建了FIFO read_fifo,用于向lucy.c发送消息。同时,lucy.c 能够通过打开peter.c创建的FIFO来得到peter.c发来的消息,peter.c 能够通过打开lucy.c创建的FIFO来得到lucy.c发来的消息。因此,lucy.c和peter.c就组成了一个简单的聊天模型。Lucy(lucy.c编译而成)必须等到Peter(peter.c编译而成)在线才能说话,然后需要等待Peter应答才能继续发话,就是一来一往的聊天模式,如果不想聊了,说quit即可。#include #inclu

54、de #include #include #include #include #include int main(void) char write_fifo_name = write-fifo; char read_fifo_name = read-fifo; int write_fd, read_fd; char buf256; int len; struct stat stat_buf; int ret = mkfifo(write_fifo_name, S_IRUSR | S_IWUSR); if ( ret = 1) printf(Fail to create FIFO %s: %s,

55、 write_fifo_name, strerror(errno); exit(1); write_fd = open(write_fifo_name, O_WRONLY); if (write_fd = 1) printf(Fail to open FIFO %s: %s, write_fifo_name, strerror(errno); exit(1); while (read_fd = open(read_fifo_name, O_RDONLY) = 1) sleep(1); while (1) printf(Lucy: ); fgets(buf, 256, stdin); bufst

56、rlen(buf)1 = 0;if (strncmp(buf,quit, 4) = 0) close(write_fd); unlink(write_fifo_name); close(read_fd); exit(0); write(write_fd, buf, strlen(buf);len = read(read_fd, buf, 256); if ( len 0) buflen = 0; printf(Peter: %sn, buf); v下面是程序peter.c:#include #include #include #include #include #include int mai

57、n(void)char write_fifo_name = read-fifo;char read_fifo_name = write-fifo;int write_fd, read_fd;char buf256; int len;int ret = mkfifo(write_fifo_name, S_IRUSR | S_IWUSR);if ( ret = 1) printf(Fail to create FIFO %s: %s, write_fifo_name, strerror(errno); exit(1); while (read_fd = open(read_fifo_name, O

58、_RDONLY) = 1) sleep(1); v下面是程序peter.c:#include #include #include #include #include #include int main(void)char write_fifo_name = read-fifo;char read_fifo_name = write-fifo;int write_fd, read_fd;char buf256; int len;int ret = mkfifo(write_fifo_name, S_IRUSR | S_IWUSR);if ( ret = 1) printf(Fail to cre

59、ate FIFO %s: %s, write_fifo_name, strerror(errno); exit(1); while (read_fd = open(read_fifo_name, O_RDONLY) = 1) sleep(1); write_fd = open(write_fifo_name, O_WRONLY); if (write_fd = 1) printf(Fail to open FIFO %s: %s, write_fifo_name, strerror(errno); exit(1); while (1) len = read(read_fd, buf, 256)

60、; if ( len 0) buflen = 0; printf(Lucy:%sn, buf); printf(Peter: );fgets(buf, 256, stdin); bufstrlen(buf)1 = 0;if (strncmp(buf,quit, 4) = 0) close(write_fd);unlink(write_fifo_name); close(read_fd); exit(0); write(write_fd, buf, strlen(buf); 以下是一小段聊天的结果示例:以下是一小段聊天的结果示例: xionglocalhost xiong$ ./lucyLucy:HelloPeter

温馨提示

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

评论

0/150

提交评论