嵌入式Linux编程入门与开发实例-第9章.ppt

上传人:小飞机 文档编号:6569753 上传时间:2023-11-13 格式:PPT 页数:114 大小:605.50KB
返回 下载 相关 举报
嵌入式Linux编程入门与开发实例-第9章.ppt_第1页
第1页 / 共114页
嵌入式Linux编程入门与开发实例-第9章.ppt_第2页
第2页 / 共114页
嵌入式Linux编程入门与开发实例-第9章.ppt_第3页
第3页 / 共114页
嵌入式Linux编程入门与开发实例-第9章.ppt_第4页
第4页 / 共114页
嵌入式Linux编程入门与开发实例-第9章.ppt_第5页
第5页 / 共114页
点击查看更多>>
资源描述

《嵌入式Linux编程入门与开发实例-第9章.ppt》由会员分享,可在线阅读,更多相关《嵌入式Linux编程入门与开发实例-第9章.ppt(114页珍藏版)》请在三一办公上搜索。

1、第9章 进程控制,操作系统的主要任务是管理计算机的软硬件资源,进程(process)在操作系统中执行特定的任务。操作系统借助进程来管理计算机的资源。而程序是存储在磁盘上包含可执行机器指令和数据的静态实体。进程或者任务是处于活动状态的计算机程序。理解和掌握进程,对于Linux下的编程来说是非常重要的。本章主要介绍进程的概念,进程的结构和进程的内存映像,以及进程的创建和退出、进程间的通信等操作。,第9章 进程控制,9.1 Linux进程,9.1.1 Linux进程概述 9.1.2 Linux进程调度 9.1.3 进程的内存映像,9.1.1 Linux进程概述,在Linux中,每个进程在创建时都会被

2、分配一个数据结构,称为进程控制块(Process Control Block,简称PCB)。PCB中包含了很多重要的信息,供系统调度和进程本身执行使用,其中最重要的莫过于进程ID(processID)了,进程ID也被称作进程标识符,是一个非负的整数,在Linux操作系统中唯一地标志一个进程。一个或多个进程可以合起来构成一个进程组(process group),一个或多个进程组可以合起来构成一个会话(session)。这样就有了对进程进行批量操作的能力,比如通过向某个进程组发送信号来实现向该组中的每个进程发送信号。,每个进程除了进程ID外还有一些其它标识信息,它们可以通过相应的函数获得。这些函数

3、的声明在unistd.h头文件中,表10-1是这些函数的说明。用户ID和组ID的相关概念如下所示:实际用户ID(uid):标识运行该进程的用户。有效用户ID(euid):标识以什么用户身份来运行进程。实际组ID(gid):它是实际用户所属的组的组ID。有效组ID(egid):有效用户所属的组的组ID。,表9-1 获取进程各种标识符的函数表,【例9-1】获取进程ID。,设计步骤1 在Vim中创建一个新工程文件,命名为“example9_1.c”。2 在“example9_1.c”中创建代码如下所示。,#include#include#includeint main()printf(Theproc

4、essIDis%d n,getpid();/*本进程*/printf(TheparentprocessIDis%dn,getppid();/*父进程*/printf(The process priority is:%dn,getpriority(PRIO_PROCESS,getpid();return 0;,3 用GCC编译运行程序结果如图9-1所示。,图9-1 获取进程ID例9-1的运行结果,1.Linux进程的组成Linux进程是由三部分组成:代码段、数据段和堆栈段。如图9-2所示。,图9-2 Linux进程组成,(1)代码段代码段存放程序的可执行代码。(2)数据段数据段存放程序的全局变量

5、、常量、静态变量。(3)堆栈段 堆栈段中的堆用于存放动态分配的内存变量,栈用于函数的调用,它存放着函数的参数、函数内部定义的局部变量。,2.Linux进程的状态 一个进程在其生存期内,可处于一组不同的状态下,称为进程状态。进程状态保存在进程任务结构的state字段中。当进程正在等待系统中的资源而处于等待状态时,则称其处于睡眠等待状态。在Linux系统中,睡眠等待状态被分为可中断的和不可中断的等待状态。,可运行状态(TASK_RUNNING)当进程正在被CPU执行,或已经准备就绪随时可由调度程序执行,则称该进程为处于运行状态(running)。进程可以在内核态运行,也可以在用户态运行。当系统资源

6、已经可用时,进程就被唤醒而进入准备运行状态,该状态称为就绪态。这些状态在内核中表示方法相同,都被称为处于 TASK_RUNNING 状态。,可中断睡眠状态(TASK_INTERRUPTIBLE)当进程处于可中断等待状态时,系统不会调度该进行执行。当系统产生一个中断或者释放了进程正在等待的资源,或者进程收到一个信号,都可以唤醒进程转换到就绪状态(运行状态)。,不可中断睡眠状态(TASK_UNINTERRUPTIBLE)与可中断睡眠状态类似。但处于该状态的进程只有被使用wake_up()函数明确唤醒时才能转换到可运行的就绪状态。暂停状态(TASK_STOPPED)当进程收到信号SIGSTOP、SI

7、GTSTP、SIGTTIN 或 SIGTTOU 时就会进入暂停状态。可向其发送SIGCONT信号让进程转换到可运行状态。处于该状态的进程将被作为进程终止来处理。僵死状态(TASK_ZOMBIE),图9-3 进程状态转换示意图,查看进程当前的状态可以使用ps命令。使用该命令可以确定有哪些进程正在运行和运行的状态、进程是否结束、进程有没有僵死、哪些进程占用了过多的资源等等。ps命令格式如下:,ps 选项,下面对命令选项进行说明-e显示所有进程;-f全格式;-h不显示标题;-l长格式;-w宽输出;-a显示终端上的所有进程,包括其他用户的进程;-r只显示正在运行的进程;-x显示没有控制终端的进程;-u

8、 以用户为主的格式来显示程序状况。,图9-4显示了ps命令执行的部分结果。,图9-4 使用ps命令显示的结果,上述ps命令显示的数据共分为4个字段,它们的说明如下:PID:进程标识(Process ID),系统就是凭这个编号来识别及处理此进程的。TTY:Teletypewriter,登录的终端机编号。TIME:此进程所消耗的CPU时间。CMD:正在执行的命令或进程名称。,9.1.2 Linux进程调度,1、调度方式2、三种调度策略3、进程调度信息4、Linux进程描述符task_struct结构 5、进程调度程序,1.调度方式 Linux中的每个进程都分配有一个相对独立的虚拟地址空间。该虚存空

9、间分为两部分:用户空间包含了进程本身的代码和数据;内核空间包含了操作系统的代码和数据。Linux采用“有条件的可剥夺”调度方式。对于普通进程,当其时间片结束时,调度程序挑选出下一个处于TASK_RUNNING状态的进程作为当前进程(自愿调度)。对于实时进程,若其优先级足够高,则会从当前的运行进程中抢占CPU成为新的当前进程(强制调度)。发生强制调度时,若进程在用户空间中运行,就会直接被剥夺CPU;若进程在内核空间中运行,即使迫切需要其放弃CPU,也仍要等到从它系统空间返回的前夕才被剥夺CPU。,2.三种调度策略(1)SCHED_OTHER:这是普通的用户进程,进程的缺省类型,采用该策略时,系统

10、为处于TASK_RUNNING状态的每个进程分配一个时间片。当时间片用完时,进程调度程序再选择下一个优先级相对较高的进程,并授予CPU使用权。(2)SCHED_FIFO:这是一种实时进程,采用该策略时,各实时进程按其进入可运行队列的顺序依次获得CPU。除了因等待某个事件主动放弃CPU,或者出现优先级更高的进程而剥夺其CPU之外,该进程将一直占用CPU运行。(3)SCHED_RR:这也是一种实时进程,采用该策略时,各实时进程按时间片轮流使用CPU。当一个运行进程的时间片用完后,进程调度程序停止其运行并将其置于可运行队列的末尾。,3.进程调度信息 调度程序利用这部分信息决定系统中哪个进程最应该运行

11、,并结合进程的状态信息保证系统运转的公平和高效。这一部分信息通常包括进程的类别(普通进程还是实时进程)、进程的优先级等等。,表9-2 进程调度信息,4.Linux进程描述符task_struct结构 为了管理进程,操作系统必须对每个进程所做的事情进行清楚地描述,为此,操作系统使用数据结构来代表处理不同的实体,这个数据结构就是通常所说的进程描述符或进程控制块,在linux系统中,这就是task_struct结构,在includelinuxsched.h文件中定义。每个进程都会被分配一个task_struct结构,它包含了这个进程的所有信息,在任何时候操作系统都能跟踪这个结构的信息,这个结构是li

12、nux内核汇总最重要的数据结构,这个结构的部分源代码及其注释如下。,struct task_struct volatile long state;/*state=-1 不能运行,state=0运行,state 0 停止*/unsigned long flags;/*进程标志*/int sigpending;/*进程上是否有待处理的信号*/mm_segment_t addr_limit;/*进程地址空间,0-0 xBFFFFFFF for user-thead,0-0 xFFFFFFFF for kernel-thread*/struct exec_domain*exec*domain;vola

13、tile long need_resched;/*调度标志,表示该进程是否需要重新调度,若非0,则当 从内核态返回到用户态,会发生调度*/unsigned long ptrace;int lock_depth;/*锁深度*/long counter;/*进程可运行的时间片*/,long nice;/*进程的基本时间片*/unsigned long policy;/*进程的调度策略有三种,实时进程:SCHED_FIFO,SCHED_RR;分时进程:SCHED_OTHER*/struct mm_struct*mm;/*进程内存管理信息*/int processor;/*若进程不在任何CPU上运行,

14、cpus_runnable 的值是0,否则是1。*/unsigned long cpus_runnable,cpus_allowed;struct list_head run_list;/*指向运行队列的指针*/unsigned long sleep_time;/*进程的睡眠时间*/struct task_struct*next_task,*prev_task;/*用于将系统中所有的进程连成一个双 向循环链表,其根是init_task*/struct mm_struct*active_mm;struct list_head local_pages;/*指向本地页面*/unsigned int

15、allocation_order,nr_local_pages;/*任务状态*/struct linux_binfmt*binfmt;/*进程所运行的可执行文件的格式*/.;,图9-5 task_struct 的数据结构,内核函数goodness()用来衡量一个处于可运行状态的进程值得运行的程度,该函数给每个处于可运行状态的进程赋予一个权值(weight),函数主体如下。,Static inline goodness(struct task_struct*pint this_cpu,struct mm_struct*this_mm)int weight;/*权值*/wight=-1;if(p-

16、policy/*返回权值为进程的counter值*/,/*如果当前进程的counter为0,则表示当前进程的时间片已用完,直接返回*/if(!weight)goto out;#Ifdef CONFIG_SMP if(p-processor=this_cpu)weight+=PROC_CHANGE_PENALTY;#Endif/*对进程权值进行微调,如果进程的内存空间使用当前正在运行的进程的内存空间,则权值额外加1*/if(p-mm=this_mm|!p-mm),Weight+=1;/*将权值加上20与进程优先级nice的差。普通进程的权值主要由counter值和nice值组成*/weight+

17、=20-p-nice;goto out;/*对实时进程进行处理,返回权值为rt_priority+1000,确保优先级高于普通进程*/weight=1000+p-rt_priority;out:return weight;/*返回权值*/;,5.进程调度程序 Linux的进程调度由调度函数schedule()完成,schedule()函数首先扫描任务数组。通过比较每个就绪态(TASK_RUNNING)任务的运行时间递减计数 counter 的值来确定当前哪个进程运行的时间最少。哪一个的值大,就表示运行时间还不长,于是就选中该进程,并使用任务切换宏函数切换到该进程运行。,9.1.3 进程的内存映

18、像,进程的内存映像,指的是内核在内存中如何存放可执行程序文件。这里的可执行程序文件和内存映像是有区别的,其区别体现在以下方面:(1)可执行程序是位于硬盘上的,而内存映像位于内存上。(2)可执行程序没有堆栈,因为只有当程序被加载到内存上的时候才会分配相应的堆栈。(3)可执行程序是静态的,因为它还没运行,但是内存映像是动态的,数据是随着运行过程改变的。,Linux下的内存映像布局一般有如下几个段(从低地址到高地址):代码段:即二进制机器代码,代码段是只读的,可以被多个进程共享。数据段:存储已初始化的变量,包括全局变量和初始化了的静态变量。未初始化数据段:存储未被初始化的静态变量,也就是BSS段。堆

19、:用于存放动态分配的变量。栈:用于函数调用,保存函数返回值,参数等等。,图9-6 程序映像的布局,9.2 进程控制,9.2.1 创建进程9.2.2 创建守护进程9.2.3 进程退出9.2.4 改变进程的优先级 9.2.5 执行新程序9.2.6 等待进程结束,Linux系统中一个进程可以在内核态(kernel mode)或用户态(user mode)下执行,并且分别使用各自独立的内核态堆栈和用户态堆栈。用户堆栈用于进程在用户态下临时保存调用函数的参数、局部变量等数据;内核堆栈则含有内核程序执行函数调用时的信息。,在Linux中主要提供了fork()、vfork()的进程创建方法,介绍如下:1、f

20、ork()函数,9.2.1 创建进程,#include#include pid_t fork(void);,fork()函数有两个返回值,即调用一次返回两次。成功调用fork()函数后,当前进程实际已经分裂为两个进程,一个是原来的父进程,另一个是刚刚创建的子进程。,【例9-2】创建进程。,设计步骤1 在Vim中创建一个新工程文件,命名为“example9_2.c”。2 在“example9_2.c”中创建代码如下所示。,#include#include#includemain()int i;if(fork()=0)/*子进程程序*/printf(This is child processn);

21、else/*父进程程序*/printf(This is father processn);,3 用GCC编译并运行结果如图9-7所示。,图9-7 用fork()函数创建进程,2、vfork()函数,#include#include pid_t vfork(void);,正确返回:在父进程中返回子进程的进程号,在子进程中返回0;错误返回:-1。,Linux系统启动时往往需要启动很多的系统服务程序,这些系统服务程序往往是运行在后台的,不受用户登录注销的影响,它们一直在运行着,这些服务程序被称为守护进程(daemon)。由于守护进程运行在后台中,不可能向终端输出相关的运行信息,因此,日志系统是守护进

22、程用于记录信息的重要手段。Linux的大多数服务器就是用守护进程的方式实现的。,9.2.2 创建守护进程,图9-8用“ps axj”命令查看系统中的进程。参数a表示不仅列当前用户的进程,也列出所有其他用户的进程,参数x表示不仅列有控制终端的进程,也列出所有无控制终端的进程,参数j表示列出与作业控制相关的信息。,图9-8 查看系统中进程的结果,要编程实现一个守护进程必须遵守如下的步骤。1调用fork()函数创建子进程后,使父进程立即退出。这样,产生的子进程将变成孤儿进程,同时,所产生的新进程将变为在后台运行。2调用setsid()函数,使得新创建的进程脱离控制终端,同时创建新的进程组,并成为该进

23、程组的首进程。由于守护进程没有控制终端,而使用fork()函数创建的子进程继承了父进程的控制终端、会话和进程组,因此,必须创建新的会话,以脱离父进程的影响。Linux系统提供了setsid()函数用于创建新的会话。,setsid()函数原型如下所示,#include pid_t setsid(void);,函数调用成功调用进程的会话ID,失败返回-1。,3使用fork()函数产生的子进程将继承父进程的当前工作目录。当进程没有结束时,其工作目录是不能被卸载的。为了防止这种问题发生,守护进程一般会将其工作目录更改到根目录下(/目录)。4关闭文件描述符,并重定向标准输入、输出和错误输出。新产生的进程

24、从父进程继承了某些打开的文件描述符,如果不使用这些文件描述符,则需要关闭它们。守护进程是运行在系统后台的,不应该在终端有任何的输出信息。可以使用dup()函数将标准输入、输出和错误输出重定向到/dev/null设备上。5将文件创建时使用的屏蔽字设置为0,很多情况下,守护进程会创建一些临时文件。出于安全性的考虑,往往不希望这些文件被别的用户查看。可以使用umask(0)将屏蔽字清零。,【例9-3】创建守护进程。,设计步骤1 在Vim中创建一个新工程文件,命名为“example9_3.c”和“example9_4.c”。2 在“example9_3.c”和“example9_4.c”中创建代码如下

25、所示。,/*example9_3.c代码如下*/#include#include#include#include#include#include#include void init_daemon(void)int pid;int i;,/*处理SIGCHLD信号。处理SIGCHLD信号并不是必须的。但对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。*/if(signal(SIGCHLD,SIG_IGN)=SIG_ERR)printf(Cant signal in init_daemon.n);e

26、xit(1);if(pid=fork()exit(0);/是父进程,结束父进程 else if(pid 0)exit(1);/fork失败,退出,/是第一子进程,后台继续执行 setsid();/第一子进程成为新的会话组长和进程组长/并与控制终端分离 if(pid=fork()exit(0);/是第一子进程,结束第一子进程 else if(pid 0)printf(fork fail.n);exit(1);/fork失败,退出/是第二子进程,继续/第二子进程不再是会话组长 for(i=0;i NOFILE;+i)/关闭打开的文件描述符 close(i);chdir(/tmp);/改变工作目录到

27、/tmp umask(0);/重设文件创建掩模 return;,/*example9_4.c代码如下*/#include#include void init_daemon(void);/守护进程初始化函数 main()FILE*fp;time_t t;init_daemon();/初始化为Daemon while(1)/每隔一分钟向test.log报告运行状态 sleep(60);/睡眠一分钟 if(fp=fopen(test.log,a)=0)t=time(0);fprintf(fp,Im here at%sn,asctime(localtime(,3 用GCC编译运行程序结果如图9-9所示

28、。,图9-9 例9-3的运行结果,1、正常退出(1)在main()函数中执行return。(2)调用exit()函数。exit()函数原型:void exit(int state);(3)调用_exit函数。(4)调用on_exit()函数 2、异常退出(1)调用abort()函数。abort()函数原型:void abort(void);(2)进程收到某个信号,而该信号使程序终止。,9.2.3 进程退出,【例9-4】进程退出。,设计步骤1 在Vim中创建一个新工程文件,命名为“example9_5.c”。2 在“example9_5.c”中创建代码如下所示。,#include#include

29、 main()printf(I am process!Now exit.n);exit(0);,3 用GCC编译运行程序结果如图9-10所示。,图9-10 使用_exit()函数退出进程,可以通过设置进程的优先级来保证进程的优先运行。相关的函数有setpriority()、getpriority()和nice()。setpriority()函数的原型为:#include#includeint setpriority(int which,int who,int prio);,9.2.4 改变进程的优先级,setpriority()函数可用来设置进程、进程组和用户的进程执行优先权。参数which有

30、三种数值,参数who 则依which值有不同定义,which who 代表的意义:PRIO_PROCESS who为进程识别码。PRIO_PGRP who 为进程的组识别码。PRIO_USER who为用户识别码。,参数prio介于-20 至20 之间。代表进程执行优先权,数值越低代表有较高的优先次序,执行会较频繁。此优先权默认是0,而只有超级用户(root)允许降低此值。执行成功则返回0,如果有错误发生返回值则为-1,错误原因存于errno。可能产生的错误有:ESRCH:参数which或who 可能有错,而找不到符合的进程。EINVAL:参数which值错误。EPERM:权限不够,无法完成设

31、置。EACCES:该调用可能降低进程的优先级。,getpriority()函数原型为:,#include#includeint getpriority(int which,int who);,该函数返回一组进程的优先级。参数which和问候组合确定返回哪一组进程的优先级。which的可能取值以及who的意义如下。PRIO_PROCESS:一个特定的进程,此时who的取值为进程ID。PRIO_PGRP:一个进程组的所有进程,此时who的取值为进程组ID。PRIO_USER:一个用户拥有的所有进程,此时参数who取值为实际用户组ID。,getpriority()函数如果调用成功返回指定进程的优先级

32、,如果出错将返回-1,并设置errno的值。Errno的可能取值如下:ESRCH:参数which或who 可能有错,而找不到符合的进程。EINVAL:参数which值错误。,nice()函数原型为:,#include int nice(int increment);,使用fork()或vfork()函数创建子程序后,子程序通常会调用exec()函数来执行另外一个程序。当进程调用exec()函数时,该进程完全由新程序代替,因为并不创建新的进程,所以前后的进程ID并未改变。Linux下exec()函数族有以下6种不同的调用形式,格式如下:#include int execve(const char

33、*path,char*const argv,char*const envp);int execv(const char*path,char*const envp);int execle(const char*path,const char*arg,.);int execl(const char*path,const char*arg,.);int exevp(const char*file,Har*const argv);int execlp(const char*file,const char*arg,.);,9.2.5 执行新程序,当子进程先于父进程退出时,如果父进程没有调用wait()和

34、waitpid()函数,子进程就会进入僵死状态。如果父进程调用了wait()或waitpid()函数,就不会使子进程变为僵死进程,这两个函数的声明如下:#include#include pid_t wait(int*status);pid_t waitpid(pid_t pid,int*status,int options);,9.2.6 等待进程结束,表9-3 检查wait()和waitpid()所返回的终止状态的宏,表9-4 waitpid()函数参数pid不同取值的对应状态,【例9-5】进程等待。,设计步骤1 在Vim中创建一个新工程文件,命名为“example9_6.c”。2 在“ex

35、ample9_6.c”中创建代码如下所示。,#include#include#include extern int errno;/引入 errno 外部变量 int main(int argc,char*argv)pid_t pid_one,pid_wait;int status;if(pid_one=fork()=-1)/调用 fork 函数 perror(fork);/如果出错,打印错误信息,if(pid_one=0)printf(my pid is%dn,getpid();sleep(1);exit(EXIT_SUCCESS);/正确退出 else pid_wait=wait(,3 用G

36、CC编译运行程序结果如图9-11所示。,图9-11 等待进程的运行结果,9.3 进程间通信,Linux下进程间通信的几个主要方法如下:(1)管道(Pipe):管道可用于具有亲缘关系进程间的通信,允许一个进程和另一个与它有共同祖先的进程之间进行通信。管道是一种半双工的通信方式,数据只能单方向流动。(2)有名管道(named pipe):也是一种半双工的通信方式。有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。有名管道在文件系统中有对应的文件名。命名管道通过命令mkfifo或系统调用mkfifo来创建。,(3)消息队列(Message queue)

37、:消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号传递信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。(4)信号量(semaphore):信号量是一个计数器,主要用于同一进程中各线程之间的信息交互和同步。信号量常常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。,(5)共享内存(shared memory):使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用

38、,来达到进程间的同步及通信。(6)信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal()外,还支持语义符合Posix.1标准的信号函数sigaction()(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction()函数重新实现了signal()函数)。(7)套接字(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系

39、统上:Linux和System V的变种都支持套接字。,9.3.1 管道9.3.2 有名管道9.3.3 消息队列9.3.4 信号量9.3.5 共享内存,管道是Linux最早使用的进程通信机制之一,管道只能实现具有亲缘关系的进程(如父进程与子进程)间的通信,而有名管道克服了这一缺点。管道是单向的,数据只能从一端写入,从另一端读取。如果要进行全双工通信,需要建立两个管道。管道还有其他一些不足,如管道没有名字,管道的缓冲区大小是受限制的,管道所传送的是无格式的字节流。要求管道的输入方和输出方事先约定好数据的格式。,9.3.1 管道,常见的管道相关函数有如下几种。1.popen()函数原型为:#inc

40、lude FILE*popen(const char*command,const char*type);该函数会调用fork()产生子进程,然后从子进程中调用/bin/sh-c来执行参数command的指令。参数type可使用“r”代表读取,“w”代表写入。依照此type值,popen()会建立管道连到子进程的标准,输出设备或标准输入设备,然后返回一个文件指针。随后进程便可利用此文件指针来读取子进程的输出设备或是写入到子进程的标准输入设备中。,2.pipe()函数原型为:#include int pipe(int filedes2);该函数会创建管道,并将文件描述词由参数filedes数组返回

41、。filedes0为管道的读取端,filedes1为管道的写入端。如果从管道写端读数据或者向管道写端读数据都将导致出错。函数执行成功返回0,否则返回-1。,3.pclose()函数原型为:#include int pclose(FILE*stream);该函数用来关闭由popen所建立的管道及文件指针,参数stream为先前由popen所返回的文件指针。如果执行成功则返回子进程的结束状态,否则返回-1。,管道的不足之处是没有名字,只能用于具有亲缘关系的进程间通信,有名管道(named pipe或FIFO)可以在互不相关的两个进程间实现彼此通信。有名管道提供一个路径名与之关联,有名管道是一个设备

42、文件。有名管道FIFO严格按照先进先出的规则,对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾,不支持lseek等文件定位操作。有名管道的创建在Shell方式下可以使用mkfifo()函数和mknod()函数。创建成功后就可以使用open()、read()、write()这些函数了。,9.3.2 有名管道,【例9-5】进程通信。,设计步骤1 在Vim中创建一个新工程文件,命名为“pipeS.c”。2 在“pipeS.c”中创建代码如下所示。,#include#include#include#include#include#include main()int pipe_fd

43、2;pid_t pid;int len=4096*2;char r_buflen;char w_buflen;char*p_wbuf;,int r_num;int w_num;int cmd;memset(r_buf,0,sizeof(r_buf);memset(w_buf,0,sizeof(r_buf);p_wbuf=w_buf;if(pipe(pipe_fd)0)/-1,失败 printf(pipe create error n);return-1;printf(nsuccess;n);,if(pid=fork()=0)/子进程 close(pipe_fd1);/子进程读,fd0用于读取管

44、道,fd1用于写入管道。while(1)r_num=read(pipe_fd0,r_buf,sizeof(r_buf);if(r_num=0)break;printf(ReadNum=%d n,r_num);/sleep(1);/验证写阻塞 close(pipe_fd0);printf(n Child process quit.n);,else if(pid0)/父进程 close(pipe_fd0);/关闭读,父进程写,fd0用于读取管道,fd1用于写入管道。/sleep(5);while(1)/w_num=write(pipe_fd1,w_buf,sizeof(w_buf);w_num=w

45、rite(pipe_fd1,w_buf,111);if(w_num!=-1)printf(WriterNum=%d n,w_num);else printf(nerrorn);sleep(1);/验证读阻塞 close(pipe_fd1);/关闭写 printf(n Parent process quit.n);,代码实例创建了父子进程,父进程写管道,子进程读管道。子进程读一次管道就休眠1秒,父进程一次写操作后将阻塞,直到子进程取走数据。父进程的写一次管道后休眠1秒,子进程一次读操作后将阻塞,直到父进程再次写数据。如果管道内的实际数据比请求读的要少,读不阻塞。程序用GCC编译成可执行文件pip

46、ieS后,在终端上运行./pipeS,程序结果如图9-12所示。,图9-12 进程通信结果,消息队列是一个存放在内核中的消息链表,每个消息队列由消息队列标识符标识。消息队列是存放在内核中的,只有在内核重启(操作系统重启)或者显示地删除一个消息队列时,该消息队列才会被真正删除。操作消息队列时用到的数据结构主要有msgbuf、msqid_ds和ipc_perm。,9.3.3 消息队列,msgbuf的定义如下:,#include structmsgbuflongmtype;/*typeofmessage*/charmtext1;/*messagetext*/;,在结构中共有两个元素:mtype指消息

47、的类型,它由一个整数来代表,并且,它只能是整数。mtext是消息数据本身。,msqid_ds定义如下:,structmsqid_dsstruct_ipc_permmsg_perm;struct_msg*msg_first;struct_msg*msg_last;_kernel_t time_tmsg_stime;_kernel_t time_tmsg_rtime;_kernel_t time_tmsg_ctime;unsigned long msg_lcbytes;unsigned long msg_lqbytes;unsigned shortmsg_cbytes;unsigned short

48、msg_qnum;unsigned shortmsg_qbytes;_kernel_ipc_pid_t msg_lspid;_kernel_ipc_pid_t msg_lrpid;,各字段含义如下:msg_perm:是一个ipc_perm的结构,保存了消息队列的存取权限、队列的用户ID、组ID等信息。msg_first:指向队列中的第一个消息。msg_last:指向队列中的最后一个消息。msg_stime:发送到队列中的最后一条消息的时间。msg_rtime:从队列中读取的最后一条消息的时间。msg_ctime:是队列最后一次改动的时间。msg_cbytes:是队列中所有消息的总长度。msg_

49、qnum:是当前队列中的消息的数目。msg_qbytes:队列中的最大的字节数。msg_lspid:发送最后一条消息的进程ID。msg_lrpid:读取队列中最后一个消息的进程的ID。,ipc_perm定义如下:,struct ipc_perm_kernel_key_t key;/*创建消息队列时用到的键值key*/_kernel_uid_t uid;/*消息队列的用户ID*/_kernel_gid_t gid;/*消息队列的组ID*/_kernel_uid_t cuid;/*创建消息队列的进程用户ID*/_kernel_gid_t cgid;/*创建消息队列的进程组ID*/_kernel_m

50、ode_t mode;/*/unsigned_short seq;/*/;,与消息队列处理相关的函数主要有以下几种。1.msgget()msgget()函数原型为:#include int msgget(key_t key,int flags);函数中参数key用来转换成一个标识符,每一个IPC对象与一个key相对应。参数flags用来决定消息队列的存储权限。,2.msgrcv()函数msgrcv()可以从队列中读取消息,函数原型如下:#include ssize_t msgrcv(int msqid,void*ptr,size_t nbytes,long type,int flag);函数中

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 生活休闲 > 在线阅读


备案号:宁ICP备20000045号-2

经营许可证:宁B2-20210002

宁公网安备 64010402000987号