资源描述
,单击此处编辑母版标题样式,单击此处编辑母版文本样式,第二级,第三级,第四级,*,嵌入式Linux编程入门与开发实例-第9章,Page,*,单击此处编辑母版标题样式,单击此处编辑母版文本样式,第二级,第三级,第四级,*,嵌入式Linux编程入门与开发实例-第9章,Page,*,第,9,章 进程控制,操作系统的主要任务是管理计算机的软硬件资源,进程(,process,)在操作系统中执行特定的任务。操作系统借助进程来管理计算机的资源。而程序是存储在磁盘上包含可执行机器指令和数据的静态实体。进程或者任务是处于活动状态的计算机程序。理解和掌握进程,对于,Linux,下的编程来说是非常重要的。,本章主要介绍进程的概念,进程的结构和进程的内存映像,以及进程的创建和退出、进程间的通信等操作。,嵌入式Linux编程入门与开发实例-第9章,第,9,章 进程控制,Linux,进程,9.1,进程控制,9.2,进程间通信,9.3,习题与练习,9.4,嵌入式Linux编程入门与开发实例-第9章,9.1 Linux,进程,9.1.1 Linux,进程概述,9.1.2 Linux,进程调度,9.1.3,进程的内存映像,嵌入式Linux编程入门与开发实例-第9章,9.1.1 Linux,进程概述,在,Linux,中,每个进程在创建时都会被分配一个数据结构,称为进程控制块(,Process Control Block,,简称,PCB,)。,PCB,中包含了很多重要的信息,供系统调度和进程本身执行使用,其中最重要的莫过于进程,ID,(,processID,)了,进程,ID,也被称作进程标识符,是一个非负的整数,在,Linux,操作系统中唯一地标志一个进程。一个或多个进程可以合起来构成一个进程组(,process group,),一个或多个进程组可以合起来构成一个会话(,session,)。这样就有了对进程进行批量操作的能力,比如通过向某个进程组发送信号来实现向该组中的每个进程发送信号,。,嵌入式Linux编程入门与开发实例-第9章,每个进程除了进程,ID,外还有一些其它标识信息,它们可以通过相应的函数获得。这些函数的声明在,unistd.h,头文件中,表,10-1,是这些函数的说明。用户,ID,和组,ID,的相关概念如下所示:,实际用户,ID,(,uid,):标识运行该进程的用户。,有效用户,ID,(,euid,):标识以什么用户身份来运行进程。,实际组,ID,(,gid,),:,它是实际用户所属的组的组,ID,。,有效组,ID,(,egid,),:,有效用户所属的组的组,ID,。,嵌入式Linux编程入门与开发实例-第9章,表,9-1,获取进程各种标识符的函数表,函数声明,功能,pid_t getpid(),获得当前进程,ID,pid_t getppid(),获得进程父进程的,ID,pid_t getuid(),获得进程的实际用户,ID,pid_t geteuid(),获得进程的有效用户,ID,pid_t getgid(),获得进程的实际组,ID,pid_t getegid(),获得进程的有效组,ID,嵌入式Linux编程入门与开发实例-第9章,【,例,9-1】,获取进程,ID,。,设计步骤,1,在,Vim,中创建一个新工程文件,命名为“,example9_1.c”,。,2,在“,example9_1.c”,中创建代码如下所示。,嵌入式Linux编程入门与开发实例-第9章,#include,#include ,#include,int main( ),printf(TheprocessIDis%d n,getpid();/*,本进程*,/printf(TheparentprocessIDis%dn , getppid();/*,父进程*,/printf(The process priority is:%dn, getpriority(PRIO_PROCESS,getpid();return 0;,3 用GCC编译运行程序结果如图9-1所示。,嵌入式Linux编程入门与开发实例-第9章,图,9-1,获取进程,ID,例,9-1,的运行结果,嵌入式Linux编程入门与开发实例-第9章,1. Linux,进程的组成,Linux,进程是由三部分组成:代码段、数据段和堆栈段。如图,9-2,所示。,图,9-2 Linux,进程组成,嵌入式Linux编程入门与开发实例-第9章,(1),代码段,代码段存放程序的可执行代码。,(2),数据段,数据段存放程序的全局变量、常量、静态变量。,(3),堆栈段,堆栈段中的堆用于存放动态分配的内存变量,栈用于函数的调用,它存放着函数的参数、函数内部定义的局部变量。,嵌入式Linux编程入门与开发实例-第9章,2. Linux,进程的状态,一个进程在其生存期内,可处于一组不同的状态下,称为进程状态。进程状态保存在进程任务结构的,state,字段中。当进程正在等待系统中的资源而处于等待状态时,则称其处于睡眠等待状态。在,Linux,系统中,睡眠等待状态被分为可中断的和不可中断的等待状态。,嵌入式Linux编程入门与开发实例-第9章,可运行状态(,TASK_RUNNING,),当进程正在被,CPU,执行,或已经准备就绪随时可由调度程序执行,则称该进程为处于运行状态(,running,)。进程可以在内核态运行,也可以在用户态运行。当系统资源已经可用时,进程就被唤醒而进入准备运行状态,该状态称为就绪态。这些状态在内核中表示方法相同, 都被称为处于,TASK_RUNNING,状态。,嵌入式Linux编程入门与开发实例-第9章,可中断睡眠状态(,TASK_INTERRUPTIBLE,),当进程处于可中断等待状态时,系统不会调度该进行执行。当系统产生一个中断或者释放了进程正在等待的资源,或者进程收到一个信号,都可以唤醒进程转换到就绪状态(运行状态)。,嵌入式Linux编程入门与开发实例-第9章,不可中断睡眠状态(,TASK_UNINTERRUPTIBLE,),与可中断睡眠状态类似。但处于该状态的进程只有被使用,wake_up(),函数明确唤醒时才能转换到可运行的就绪状态。,暂停状态(,TASK_STOPPED,),当进程收到信号,SIGSTOP,、,SIGTSTP,、,SIGTTIN,或,SIGTTOU,时就会进入暂停状态。可向其发送,SIGCONT,信号让进程转换到可运行状态。处于该状态的进程将被作为进程终止来处理。,僵死状态(,TASK_ZOMBIE,),嵌入式Linux编程入门与开发实例-第9章,图,9-3,进程状态转换示意图,嵌入式Linux编程入门与开发实例-第9章,查看进程当前的状态可以使用,ps,命令。使用该命令可以确定有哪些进程正在运行和运行的状态、进程是否结束、进程有没有僵死、哪些进程占用了过多的资源等等。,ps,命令格式如下:,ps 选项,嵌入式Linux编程入门与开发实例-第9章,下面对命令选项进行说明,-e,显示所有进程;,-f,全格式;,-h,不显示标题;,-l,长格式;,-w,宽输出;,-a,显示终端上的所有进程,包括其他用户的进程;,-r,只显示正在运行的进程;,-x,显示没有控制终端的进程;,-u,以用户为主的格式来显示程序状况。,嵌入式Linux编程入门与开发实例-第9章,图,9-4,显示了,ps,命令执行的部分结果。,图,9-4,使用,ps,命令显示的结果,嵌入式Linux编程入门与开发实例-第9章,上述,ps,命令显示的数据共分为,4,个字段,它们的说明如下:,PID,:进程标识(,Process ID,),系统就是凭这个编号来识别及处理此进程的。,TTY,:,Teletypewriter,,登录的终端机编号。,TIME,:此进程所消耗的,CPU,时间。,CMD,: 正在执行的命令或进程名称。,嵌入式Linux编程入门与开发实例-第9章,9.1.2 Linux,进程调度,1,、调度方式,2,、三种调度策略,3,、进程调度信息,4,、,Linux,进程描述符,task_struct,结构,5,、进程调度程序,嵌入式Linux编程入门与开发实例-第9章,1.,调度方式,Linux,中的每个进程都分配有一个相对独立的虚拟地址空间。该虚存空间分为两部分:用户空间包含了进程本身的代码和数据;内核空间包含了操作系统的代码和数据。,Linux,采用“有条件的可剥夺”调度方式。对于普通进程,当其时间片结束时,调度程序挑选出下一个处于,TASK_RUNNING,状态的进程作为当前进程(自愿调度)。对于实时进程,若其优先级足够高,则会从当前的运行进程中抢占,CPU,成为新的当前进程(强制调度)。发生强制调度时,若进程在用户空间中运行,就会直接被剥夺,CPU,;若进程在内核空间中运行,即使迫切需要其放弃,CPU,,也仍要等到从它系统空间返回的前夕才被剥夺,CPU,。,嵌入式Linux编程入门与开发实例-第9章,2.,三种调度策略,(,1,),SCHED_OTHER,:这是普通的用户进程,进程的缺省类型,采用该策略时,系统为处于,TASK_RUNNING,状态的每个进程分配一个时间片。当时间片用完时,进程调度程序再选择下一个优先级相对较高的进程,并授予,CPU,使用权。,(,2,),SCHED_FIFO,:这是一种实时进程,采用该策略时,各实时进程按其进入可运行队列的顺序依次获得,CPU,。除了因等待某个事件主动放弃,CPU,,或者出现优先级更高的进程而剥夺其,CPU,之外,该进程将一直占用,CPU,运行。,(,3,),SCHED_RR,:这也是一种实时进程,采用该策略时,各实时进程按时间片轮流使用,CPU,。当一个运行进程的时间片用完后,进程调度程序停止其运行并将其置于可运行队列的末尾。,嵌入式Linux编程入门与开发实例-第9章,3.,进程调度信息,调度程序利用这部分信息决定系统中哪个进程最应该运行,并结合进程的状态信息保证系统运转的公平和高效。这一部分信息通常包括进程的类别(普通进程还是实时进程)、进程的优先级等等。,嵌入式Linux编程入门与开发实例-第9章,表,9-2,进程调度信息,域名,含义,need_resched,调度标志,Nice,静态优先级,Counter,动态优先级,Policy,调度策略,rt_priority,实时优先级,嵌入式Linux编程入门与开发实例-第9章,4. Linux,进程描述符,task_struct,结构,为了管理进程,操作系统必须对每个进程所做的事情进行清楚地描述,为此,操作系统使用数据结构来代表处理不同的实体,这个数据结构就是通常所说的进程描述符或进程控制块,在,linux,系统中,这就是,task_struct,结构,在,includelinuxsched.h,文件中定义。每个进程都会被分配一个,task_struct,结构,它包含了这个进程的所有信息,在任何时候操作系统都能跟踪这个结构的信息,这个结构是,linux,内核汇总最重要的数据结构,这个结构的部分源代码及其注释如下。,嵌入式Linux编程入门与开发实例-第9章,struct task_struct,volatile long state; /* state=-1,不能运行,,state=0,运行, state 0,停止*,/,unsigned long flags; /*,进程标志 *,/,int sigpending; /*,进程上是否有待处理的信号*,/,mm_segment_t addr_limit; /*,进程地址空间,0-0xBFFFFFFF for user-thead,0- 0xFFFFFFFF for kernel-thread*/,struct exec_domain*exec*domain;,volatile long need_resched; /*,调度标志,表示该进程是否需要重新调度,若非,0,则当,从内核态返回到用户态,会发生调度*,/,unsigned long ptrace;,int lock_depth; /*,锁深度 *,/,long counter; /*,进程可运行的时间片*,/,嵌入式Linux编程入门与开发实例-第9章,long nice; /*,进程的基本时间片*,/,unsigned long policy; /*,进程的调度策略有三种,实时进程,:SCHED_FIFO ,SCHED_RR ;,分时进程,:SCHED_OTHER*/,struct mm_struct *mm; /*,进程内存管理信息*,/,int processor;/*,若进程不在任何,CPU,上运行,,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 allocation_order, nr_local_pages;/*,任务状态 *,/,struct linux_binfmt *binfmt; /*,进程所运行的可执行文件的格式*,/,.,;,嵌入式Linux编程入门与开发实例-第9章,图,9-5 task_struct,的数据结构,嵌入式Linux编程入门与开发实例-第9章,内核函数,goodness(),用来衡量一个处于可运行状态的进程值得运行的程度,该函数给每个处于可运行状态的进程赋予一个权值(,weight,),函数主体如下。,Static inline goodness (struct task_struct * pint this_cpu, struct mm_struct *this_mm),int weight;/*,权值*,/,wight=-1;,if (p-policy & SCHED_YIELD),goto out;/*,系统调用,SCHED_YIELD,表示为“礼让”进程,其权值为,-1*/,if (p-policy=SCHED_OTHER),weight=p-counter; /*,返回权值为进程的,counter,值*,/,嵌入式Linux编程入门与开发实例-第9章,/*,如果当前进程的,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),嵌入式Linux编程入门与开发实例-第9章,Weight+=1;,/*,将权值加上,20,与进程优先级,nice,的差。普通进程的权值主要由,counter,值和,nice,值组成*,/,weight+=20-p-nice;,goto out;,/*,对实时进程进行处理,返回权值为,rt_priority+1000,,确保优先级高于普通进程*,/,weight=1000+p-rt_priority;,out:,return weight;/*,返回权值*,/,;,嵌入式Linux编程入门与开发实例-第9章,5.,进程调度程序,Linux,的进程调度由调度函数,schedule(),完成,,schedule(),函数首先扫描任务数组。通过比较每个就绪态(,TASK_RUNNING,)任务的运行时间递减计数,counter,的值来确定当前哪个进程运行的时间最少。哪一个的值大,就表示运行时间还不长,于是就选中该进程,并使用任务切换宏函数切换到该进程运行。,嵌入式Linux编程入门与开发实例-第9章,9.1.3,进程的内存映像,进程的内存映像,指的是内核在内存中如何存放可执行程序文件。这里的可执行程序文件和内存映像是有区别的,其区别体现在以下方面:,(1),可执行程序是位于硬盘上的,而内存映像位于内存上。,(2),可执行程序没有堆栈,因为只有当程序被加载到内存上的时候才会分配相应的堆栈。,(3),可执行程序是静态的,因为它还没运行,但是内存映像是动态的,数据是随着运行过程改变的。,嵌入式Linux编程入门与开发实例-第9章,Linux,下的内存映像布局一般有如下几个段(从低地址到高地址):,代码段: 即二进制机器代码,代码段是只读的,可以被多个进程共享。,数据段: 存储已初始化的变量,包括全局变量和初始化了的静态变量。,未初始化数据段: 存储未被初始化的静态变量,也就是,BSS,段。,堆: 用于存放动态分配的变量。,栈: 用于函数调用,保存函数返回值,参数等等。,嵌入式Linux编程入门与开发实例-第9章,图,9-6,程序映像的布局,嵌入式Linux编程入门与开发实例-第9章,9.2,进程控制,9.2.1,创建进程,9.2.2,创建守护进程,9.2.3,进程退出,9.2.4,改变进程的优先级,9.2.5,执行新程序,9.2.6,等待进程结束,嵌入式Linux编程入门与开发实例-第9章,Linux,系统中一个进程可以在内核态(,kernel mode,)或用户态(,user mode,)下执行,并且分别使用各自独立的内核态堆栈和用户态堆栈。用户堆栈用于进程在用户态下临时保存调用函数的参数、局部变量等数据;内核堆栈则含有内核程序执行函数调用时的信息。,嵌入式Linux编程入门与开发实例-第9章,在,Linux,中主要提供了,fork(),、,vfork(),的进程创建方法,介绍如下,:,1,、,fork(),函数,9.2.1,创建进程,# include ,# include ,pid_t fork(void);,fork(),函数有两个返回值,即调用一次返回两次。成功调用,fork(),函数后,当前进程实际已经分裂为两个进程,一个是原来的父进程,另一个是刚刚创建的子进程。,嵌入式Linux编程入门与开发实例-第9章,【,例,9-2】,创建进程,。,设计步骤,1,在,Vim,中创建一个新工程文件,命名为“,example9_2.c”,。,2,在“,example9_2.c”,中创建代码如下所示。,嵌入式Linux编程入门与开发实例-第9章,#include,#include ,#include,main(),int i;,if ( fork() = 0 ) ,/*,子进程程序 *,/,printf(This is child processn);,else,/*,父进程程序*,/,printf(This is father processn);,嵌入式Linux编程入门与开发实例-第9章,3 用GCC编译并运行结果如图9-7所示。,图,9-7,用,fork(),函数创建进程,嵌入式Linux编程入门与开发实例-第9章,2,、,vfork(),函数,#include ,#include ,pid_t vfork(void);,正确返回:在父进程中返回子进程的进程号,在子进程中返回,0,;错误返回:,-1,。,嵌入式Linux编程入门与开发实例-第9章,Linux,系统启动时往往需要启动很多的系统服务程序,这些系统服务程序往往是运行在后台的,不受用户登录注销的影响,它们一直在运行着,这些服务程序被称为守护进程(,daemon,)。由于守护进程运行在后台中,不可能向终端输出相关的运行信息,因此,日志系统是守护进程用于记录信息的重要手段。,Linux,的大多数服务器就是用守护进程的方式实现的。,9.2.2,创建守护进程,嵌入式Linux编程入门与开发实例-第9章,图,9-8,用“,ps axj”,命令查看系统中的进程。参数,a,表示不仅列当前用户的进程,也列出所有其他用户的进程,参数,x,表示不仅列有控制终端的进程,也列出所有无控制终端的进程,参数,j,表示列出与作业控制相关的信息。,嵌入式Linux编程入门与开发实例-第9章,图,9-8,查看系统中进程的结果,嵌入式Linux编程入门与开发实例-第9章,要编程实现一个守护进程必须遵守如下的步骤。,1,调用,fork(),函数创建子进程后,使父进程立即退出。这样,产生的子进程将变成孤儿进程,同时,所产生的新进程将变为在后台运行。,2,调用,setsid(),函数,使得新创建的进程脱离控制终端,同时创建新的进程组,并成为该进程组的首进程。由于守护进程没有控制终端,而使用,fork(),函数创建的子进程继承了父进程的控制终端、会话和进程组,因此,必须创建新的会话,以脱离父进程的影响。,Linux,系统提供了,setsid(),函数用于创建新的会话。,嵌入式Linux编程入门与开发实例-第9章,setsid()函数原型如下所示,#include ,pid_t setsid(void);,函数调用成功调用进程的会话ID,失败返回-1。,嵌入式Linux编程入门与开发实例-第9章,3,使用,fork(),函数产生的子进程将继承父进程的当前工作目录。当进程没有结束时,其工作目录是不能被卸载的。为了防止这种问题发生,守护进程一般会将其工作目录更改到根目录下(,/,目录)。,4,关闭文件描述符,并重定向标准输入、输出和错误输出。新产生的进程从父进程继承了某些打开的文件描述符,如果不使用这些文件描述符,则需要关闭它们。守护进程是运行在系统后台的,不应该在终端有任何的输出信息。可以使用,dup(),函数将标准输入、输出和错误输出重定向到,/dev/null,设备上。,5,将文件创建时使用的屏蔽字设置为,0,,很多情况下,守护进程会创建一些临时文件。出于安全性的考虑,往往不希望这些文件被别的用户查看。可以使用,umask,(,0,)将屏蔽字清零。,嵌入式Linux编程入门与开发实例-第9章,【,例,9-3】,创建守护进程。,设计步骤,1,在,Vim,中创建一个新工程文件,命名为“,example9_3.c”,和“,example9_4.c”,。,2,在“,example9_3.c”,和“,example9_4.c”,中创建代码如下所示。,嵌入式Linux编程入门与开发实例-第9章,/* example9_3.c,代码如下*,/,#include ,#include ,#include ,#include ,#include ,#include,#include ,void init_daemon(void),int pid;,int i;,嵌入式Linux编程入门与开发实例-第9章,/*,处理,SIGCHLD,信号。处理,SIGCHLD,信号并不是必须的。但对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(,zombie,)从而占用系统资源。,*,/,if(signal(SIGCHLD,SIG_IGN) = SIG_ERR),printf(Cant signal in init_daemon.n);,exit(1);,if(pid=fork(),exit(0);/,是父进程,结束父进程,else if(pid 0),exit(1);/fork,失败,退出,嵌入式Linux编程入门与开发实例-第9章,/,是第一子进程,后台继续执行,setsid();/,第一子进程成为新的会话组长和进程组长,/,并与控制终端分离,if(pid=fork(),exit(0);/,是第一子进程,结束第一子进程,else if(pid 0),printf(fork fail.n);,exit(1);/fork,失败,退出,/,是第二子进程,继续,/,第二子进程不再是会话组长,for(i=0;i=0),t=time(0);,fprintf(fp,Im here at %sn,asctime(localtime(,fclose(fp);,嵌入式Linux编程入门与开发实例-第9章,3 用GCC编译运行程序结果如图9-9所示。,图,9-9,例,9-3,的运行结果,嵌入式Linux编程入门与开发实例-第9章,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,进程退出,嵌入式Linux编程入门与开发实例-第9章,【,例,9-4】,进程退出。,设计步骤,1,在,Vim,中创建一个新工程文件,命名为“,example9_5.c”,。,2,在“,example9_5.c”,中创建代码如下所示。,#include,#include ,main(),printf (I am process! Now exit.n);,exit(0);,嵌入式Linux编程入门与开发实例-第9章,3 用GCC编译运行程序结果如图9-10所示。,图,9-10,使用,_exit(),函数退出进程,嵌入式Linux编程入门与开发实例-第9章,可以通过设置进程的优先级来保证进程的优先运行。相关的函数有,setpriority(),、,getpriority(),和,nice(),。,setpriority(),函数的原型为:,#include,#include,int setpriority(int which,int who, int prio);,9.2.4,改变进程的优先级,嵌入式Linux编程入门与开发实例-第9章,setpriority(),函数可用来设置进程、进程组和用户的进程执行优先权。参数,which,有三种数值,参数,who,则依,which,值有不同定义,,which who,代表的意义:,PRIO_PROCESS who,为进程识别码。,PRIO_PGRP who,为进程的组识别码。,PRIO_USER who,为用户识别码。,嵌入式Linux编程入门与开发实例-第9章,参数,prio,介于,-20,至,20,之间。代表进程执行优先权,数值越低代表有较高的优先次序,执行会较频繁。此优先权默认是,0,,而只有超级用户(,root,)允许降低此值。,执行成功则返回,0,,如果有错误发生返回值则为,-1,,错误原因存于,errno,。可能产生的错误有:,ESRCH,:参数,which,或,who,可能有错,而找不到符合的进程。,EINVAL,:参数,which,值错误。,EPERM,:权限不够,无法完成设置。,EACCES,:该调用可能降低进程的优先级。,嵌入式Linux编程入门与开发实例-第9章,getpriority()函数原型为:,#include,#include,int getpriority(int which,int who);,该函数返回一组进程的优先级。参数,which,和问候组合确定返回哪一组进程的优先级。,which,的可能取值以及,who,的意义如下。,PRIO_PROCESS:,一个特定的进程,此时,who,的取值为进程,ID,。,PRIO_PGRP:,一个进程组的所有进程,此时,who,的取值为进程组,ID,。,PRIO_USER:,一个用户拥有的所有进程,此时参数,who,取值为实际用户组,ID,。,嵌入式Linux编程入门与开发实例-第9章,getpriority(),函数如果调用成功返回指定进程的优先级,如果出错将返回,-1,,并设置,errno,的值。,Errno,的可能取值如下:,ESRCH,:参数,which,或,who,可能有错,而找不到符合的进程。,EINVAL,:参数,which,值错误。,nice()函数原型为:,#include ,int nice (int increment);,嵌入式Linux编程入门与开发实例-第9章,使用,fork(),或,vfork(),函数创建子程序后,子程序通常会调用,exec(),函数来执行另外一个程序。当进程调用,exec(),函数时,该进程完全由新程序代替,因为并不创建新的进程,所以前后的进程,ID,并未改变。,Linux,下,exec(),函数族有以下,6,种不同的调用形式,格式如下:,#include ,int execve(const char*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,执行新程序,嵌入式Linux编程入门与开发实例-第9章,当子进程先于父进程退出时,如果父进程没有调用,wait(),和,waitpid(),函数,子进程就会进入僵死状态。如果父进程调用了,wait(),或,waitpid(),函数,就不会使子进程变为僵死进程,这两个函数的声明如下:,#include ,#include ,pid_t wait(int *status);,pid_t waitpid(pid_t pid,int *status,int options);,9.2.6,等待进程结束,嵌入式Linux编程入门与开发实例-第9章,表,9-3,检查,wait(),和,waitpid(),所返回的终止状态的宏,宏定义,说明,WIFEXITED(stat_val),若子进程是正常结束的,该宏返回一个非零值,表示真。若子进程异常结束,返回零,表示假,WEXITSTATUS(stat_val),若,WIFEXITED,返回值非零,获得子进程由,exit(),返回的结束代码,WIFSIGNALED(stat_val),若子进程因为信号而终止,它就取得一个非零值,表示真,WTERMSIG(stat_val),如果宏,WIFSIGNALED,的值非零,该宏返回使子进程异常终止的信号代码,WIFSTOPPED(stat_val),若子进程暂停,它就取得一个非零值,表示真,WSTOPSIG(stat_val),若,WIFSTOPPED,非零,取得引发进程暂停的信号代码,嵌入式Linux编程入门与开发实例-第9章,表,9-4 waitpid(),函数参数,pid,不同取值的对应状态,取值,意义,pid0,等待进程,ID,等于,pid,的子进程退出,pid,0,等待组,ID,等于目前进程的组,ID,的任何子进程,pid,-1,等待组,ID,等于,pid,绝对值的任何子进程,pid,-1,等待任何子进程,嵌入式Linux编程入门与开发实例-第9章,【,例,9-5】,进程等待,。,设计步骤,1,在,Vim,中创建一个新工程文件,命名为“,example9_6.c”,。,2,在“,example9_6.c”,中创建代码如下所示。,嵌入式Linux编程入门与开发实例-第9章,#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); /,如果出错,打印错误信息,嵌入式Linux编程入门与开发实例-第9章,if(pid_one=0),printf(my pid is %dn,getpid();,sleep(1);,exit(EXIT_SUCCESS); /,正确退出,else,pid_wait=wait( /,等待子进程结束,if(WIFEXITED(status) /,使用,WIFEXITED,宏,printf(wait on pid:%d,return value is:%4xn,pid_wait,WEXITSTATUS(status);,else if(WIFSIGNALED(status),printf(wait on pid:%d,return value is:%4xn,pid_wait,WIFSIGNALED(status);,return 0;,嵌入式Linux编程入门与开发实例-第9章,3,用,GCC,编译运行程序结果如图,9-11,所示。,图,9-11,等待进程的运行结果,嵌入式Linux编程入门与开发实例-第9章,9.3,进程间通信,Linux,下进程间通信的几个主要方法如下:,(1),管道(,Pipe,):管道可用于具有亲缘关系进程间的通信,允许一个进程和另一个与它有共同祖先的进程之间进行通信。管道是一种半双工的通信方式,数据只能单方向流动。,(2),有名管道(,named pipe,):也是一种半双工的通信方式。有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。有名管道在文件系统中有对应的文件名。命名管道通过命令,mkfifo,或系统调用,mkfifo,来创建。,嵌入式Linux编程入门与开发实例-第9章,(3),消息队列(,Message queue,):消息队列是消息的链接表,包括,Posix,消息队列,system V,消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号传递信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。,(4),信号量(,semaphore,):信号量是一个计数器,主要用于同一进程中各线程之间的信息交互和同步。信号量常常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。,嵌入式Linux编程入门与开发实例-第9章,(5),共享内存(,shared memory,):使得多个进程可以访问同一块内存空间,是最快的可用,IPC,形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及通信。,(6),信号(,Signal,):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;,linux,除了支持,Unix,早期信号语义函数,sigal(),外,还支持语义符合,Posix.1,标准的信号函数,sigaction(),(实际上,该函数是基于,BSD,的,,BSD,为了实现可靠信号机制,又能够统一对外接口,用,sigaction(),函数重新实现了,signal(),函数)。,(7),套接字(,Socket,):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由,Unix,系统的,BSD,分支开发出来的,但现在一般可以移植到其它类,Unix,系统上:,Linux,和,System V,的变种都支持套接字。,嵌入式Linux编程入门与开发实例-第9章,9.3.1,管道,9.3.2,有名管道,9.3.3,消息队列,9.3.4,信号量,9.3.5,共享内存,嵌入式Linux编程入门与开发实例-第9章,管道是,Linux,最早使用的进程通信机制之一,管道只能实现具有亲缘关系的进程(如父进程与子进程)间的通信,而有名管道克服了这一缺点。管道是单向的,数据只能从一端写入,从另一端读取。如果要进行全双工通信,需要建立两个管道。管道还有其他一些不足,如管道没有名字,管道的缓冲区大小是受限制的,管道所传送的是无格式的字节流。要求管道的输入方和输出方事先约定好数据的格式。,9.3.1,管道,嵌入式Linux编程入门与开发实例-第9章,常见的管道相关函数有如下几种。,1.popen(),函数原型为:,#include ,FILE *popen(const char *command, const char *type);,该函数会调用,fork(),产生子进程,然后从子进程中调用,/bin/sh-c,来执行参数,command,的指令。参数,type,可使用“,r”,代表读取,“,w”,代表写入。依照此,type,值,,popen(),会建立管道连到子进程的标准,输出设备或标准输入设备,然后返回一个文件指针。随后进程便可利用此文件指针来读取子进程的输出设备或是写入到子进程的标准输入设备中。,嵌入式Linux编程入门与开发实例-第9章,2. pipe(),函数原型为:,#include ,int pipe(int 2);,该函数会创建管道,并将文件描述词由参数数组返回。,0,为管道的读取端,,1,为管道的写入端。如果从管道写端读数据或者向管道写端读数据都将导致出错。函数执行成功返回,0,,否则返回,-1,。,嵌入式Linux编程入门与开发实例-第9章,3.pclose(),函数原型为:,#include ,int pclose(FILE*stream);,该函数用来关闭由,popen,所建立的管道及文件指针,参数,stream,为先前由,popen,所返回的文件指针。如果执行成功则返回子进程的结束状态,否则返回,-1,。,嵌入式Linux编程入门与开发实例-第9章,管道的不足之处是没有名字,只能用于具有亲缘关系的进程间通信,有名管道(,named pipe,或,FIFO,)可以在互不相关的两个进程间实现彼此通信。有名管道提供一个路径名与之关联,有名管道是一个设备文件。有名管道,FIFO,严格按照先进先出的规则,对管道及,FIFO,的读总是从开始处返回数据,对它们的写则把数据添加到末尾,不支持,lseek,等文件定位操作。有名管道的创建在,Shell,方式下可以使用,mkfifo(),函数和,mknod(),函数。创建成功后就可以使用,open(),、,read(),、,write(),这些函数了。,9.3.2,有名管道,嵌入式Linux编程入门与开发实例-第9章,【,例,9-5】,进程通信,。,设计步骤,1,在,Vim,中创建一个新工程文件,命名为“,pipeS.c”,。,2,在“,pipeS.c”,中创建代码如下所示。,嵌入式Linux编程入门与开发实例-第9章,#include ,#include ,#include ,#include ,#include ,#include ,main(),int pipe_fd2;,pid_t pid;,int len = 4096*2;,char r_buflen;,char w_buflen;,char* p_wbuf;,嵌入式Linux编程入门与开发实例-第9章,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)/,父进程,close(pipe_fd0);/,关闭读,父进程写,,fd0,用于读取管道,,fd1,用于写入管道。,/sleep(5);,while(1),/w_num = write(pipe_fd1,w_buf,sizeof(w_buf);,w_num = write(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);,嵌入式Linux编程入门与开发实例-第9章,代码实例创建了父子进程,父进程写管道,子进程读管道。子进程读一次管道就休眠,1,秒,父进程一次写操作后将阻塞,直到子进程取走数据。父进程的写一次管道后休眠,1,秒,子进程一次读操作后将阻塞,直到父进程再次写数据。如果管道内的实际数据比请求读的要少,读不阻塞。程序用,GCC,编译成可执行文件,pipieS,后,在终端上运行,./pipeS,,程序结果如图,9-12,所示。,嵌入式Linux编程入门与开发实例-第9章,图,9-12,进程通信结果,嵌入式Linux编程入门与开发实例-第9章,消息队列是一个存放在内核中的消息链表,每个消息队列由消息队列标识符标识。消息队列是存放在内核中的,只有在内核重启(操作系统重启)或者显示地删除一个消息队列时,该消息队列才会被真正删除。操作消息队列时用到的数据结构主要有,msgbuf,、,msqid_ds,和,ipc_perm,。,9.3.3,消息队列,嵌入式Linux编程入门与开发实例-第9章,msgbuf的定义如下:,#include ,structmsgbuf,longmtype;/*typeofmessage*/,charmtext1;/*messagetext*/,;,在结构中共有两个元素:,mtype,指消息的类型,它由一个整数来代表,并且,它只能是整数。,mtext,是消息数据本身。,嵌入式Linux编程入门与开发实例-第9章,msqid_ds定义如下:,structmsqid_ds,struct_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 shortmsg_qnum;,unsigned shortmsg_qbytes;,_kernel_ipc_pid_t msg_lspid;,_kernel_ipc_pid_t msg_lrpid;,;,嵌入式Linux编程入门与开发实例-第9章,各字段含义如下:,msg_perm:,是一个,ipc_perm,的结构,保存了消息队列的存取权限、队列的用户,ID,、组,ID,等信息。,msg_first,:指向队列中的第一个消息。,msg_last,:指向队列中的最后一个消息。,msg_stime,:发送到队列中的最后一条消息的时间。,msg_rtime,:从队列中读取的最后一条消息的时间。,msg_ctime,:是队列最后一次改动的时间。,msg_cbytes,:是队列中所有消息的总长度。,msg_qnum,:是当前队列中的消息的数目。,msg_qbytes,:队列中的最大的字节数。,msg_lspid,:发送最后一条消息的进程,ID,。,msg_lrpid,:读取队列中最后一个消息的进程的,ID,。,嵌入式Linux编程入门与开发实例-第9章,ipc_perm定义如下:,struct ipc_perm,_kernel_key_t
展开阅读全文