1多进程、多线程编程--Linux下多任务2主要内容1.什么是多任务(1)进程级多任务(2)线程级多任务(3)多任务处理的特点2.进程(1)进程的概念(2)文件描述符共享(3)Vfork()函数(4)exec()函数族(5)执行新程序(6)进程的终止(7)进程的退出状态(8)进程的通信31.什么是多任务当操作系统使用某种策略允许两个或更多进程并发共享一个CPU时,它称做多任务运行,或多道程序运行。在规定的时间片周期或某些事情发生前,一直执行某个进程。然后,操作系统切换到另一个进程。这种切换十分迅速,给人一种这些进程是同时执行的错觉。而事实上,同一时刻在一个CPU上只能激活一个进程。这种进程间的切换在所有进程完成前一直进行。并发共享策略决定何时切换进程。改策略由操作系统或其他进程强制执行。多任务可以分为三个级别:对话级、进程级、线程级。4(1)进程级多任务在对话中可以并发激活多个进程,这些进程相互合作来完成一个最终目标。所有的进程共享CPU运行,一个进程运行一段时间,然后另一个进程再运行一段时间。操作系统控制进程之间的转换,直到所有的进程运行完成。对于这样一种多个进程并发执行的多任务实现方式,称作进程级多任务。5进程是运行着的程序,是操作系统执行任务的基本单位。进程具备文本、数据和堆栈片段,以及它自己的资源。资源可以是文件、对象句柄、设备、信号量、互斥量、管道,等等。操作系统管理进程运行出错不会影响到别的进程运行。两个进程之间可以通过管道等方式通信,或者通过信号量的工具同步运行。因此,进程是实现多任务处理的核心单元。什么是进程?6(2)线程级多任务进程完成单独的任务,每个任务又可能有自己的控制流程。这些流程由轻量级的进程构成,称作线程。进程的线程并发执行称作线程级多任务。7在窗口系统中每时每刻都在进行着上下文切换,而进程级的上下文切换代价十分昂贵,频繁地切换不但不能体现多任务系统的优势,反而降低了系统的整体反应速度。线程是轻量级的进程,它由进程创建,并与创建它的进程工作在同一内存空间中,不但可以与同一进程中的其他线程共享数据和文件描述符,而且线程间的切换过程也是十分快捷和低成本的。因此越来越多的多任务处理在底层都采取线程来实现。你的程序选择进程?还是线程?8(3)多任务处理的特点对话间的多任务是一个高级别的多任务,它受用户控制。进程间的多任务以及多线程在低级别上实现,由设计它的程序员控制。程序员创建进程,并决定每个进程的线程数,任务的优先权,以及什么时候挂起、什么时候终止。92、开发多进程程序(1)进程的概念:一般把进程定义成正在运行的程序的实例,简单的说,进程就是一个正在运行的程序。10(2)进程环境和属性在Linux系统,C程序总是从main()函数开始的,当用户编写好的程序在运行的时候,操作系统会使用exec()函数运行程序,在调用main()函数之前,exec()系统调用会先调用一个特殊的启动例程,负责从操作系统内核读取程序的命令行参数,为main()函数准备好工作环境。在bash下,可以执行export查看本机支持的环境变量名称和内容。11(3)Linux内核的进程管理创建新进程fork&vfork执行程序exec…进程终止exit…12进程描述符每个进程都有一个非负整型的唯一进程ID进程ID号为0表示调度进程,常常被称为交换进程(swapper)。该进程并不执行任何磁盘上的程序—它是内核的一部分,因此也被称为系统进程。进程ID号为1通常是init进程,在自举过程结束时由内核调用。init通常读与系统有关的初始化文件(/etc/rc*文件),并将系统引导到一个状态(例如多用户)。init进程决不会终止。它是一个普通的用户进程(与交换进程不同,它不是内核中的系统进程),但是它以超级用户特权运行。13与进程ID相关的API一个进程除了能获得操作系统提供的环境变量外,还具备自身的基本属性,主要包括:进程号(PID:ProcessID):操作系统通过进程号标识一个用户进程父进程号(PPID:ParentProcessID):Linux系统中,除了init进程外,所有进程都是通过init进程创建的,同时,进程又可以创建其他进程,最终形成了一个倒树形结构,每个进程都会有自己的父进程,通过父进程号标识。进程组号(PGID:ProcessGroupID):操作系统允许对进程分组,不同的进程通过进程组号标识。真实用户号(UID:UserID):用户唯一标识号,用于标识一个用户。真实组号(GID:GroupID):用户的唯一标识号,用于标识一个用户组。有效用户号(EUID:EffectiveUserID):以其他用户身份访问文件使用。有效组号(EGID:EffectiveGroupID):以其他用户组身份访问文件使用14与进程ID相关的API#includesys/types.h#includeunistd.hpid_tgetpid(void);返回:调用进程的进程IDpid_tgetppid(void);返回:调用进程的父进程IDuid_tgetuid(void);返回:调用进程的实际用户IDuid_tgeteuid(void);返回:调用进程的有效用户IDgid_tgetgid(void);返回:调用进程的实际组IDgid_tgetegid(void);返回:调用进程的有效组ID15创建进程Linux系统通过fork()系统调用创建一个进程,fork()函数定义如下:#includesys/types.h#includeunistd.hpid_tfork(void);返回:子进程中为0,父进程中为子进程ID,出错为-1该函数被调用一次,但返回两次。两次返回的区别是子进程的返回值是0,而父进程的返回值则是新子进程的进程ID。为什么将子进程ID返回给父进程?16fork创建进程过程17fork在内核究竟干了那些事情子进程和父进程继续执行fork之后的指令。子进程是父进程的复制品。子进程获得父进程数据空间、堆和栈的复制品。子进程所拥有这些数据的拷贝。18fork的执行流程检查可用的内核资源取一个空闲的进程表项和唯一的PID号检查用户有没有过多的运行进程将子进程的状态设为“创建”状态将父进程的进程表项中的数据拷贝到子进程表项中当前目录的索引节点和改变的根目录的引用计数加1文件表中的打开文件的引用数加119fork的执行流程(续)在内存中作父进程上下文的拷贝在子进程的系统级上下文中压入虚拟系统级上下文层;If(正在执行的进程是父进程)将子进程状态设为“就绪”状态return子进程的PIDelse初始化计时域return020创建进程例子#includesys/types.h#includeunistd.h#includestdio.h#includestdlib.hintmain(){pid_tpid;pid=fork();//创建进程if(-1==pid){//创建进程失败printf(Errortocreatenewprocess!\n);return0;}elseif(pid==0){//子进程printf(Childprocess!\n);}else{//父进程printf(Parentprocess!ChildprocessID:%d\n,pid);}return0;}21fork出错的原因系统中已经有了太多的进程该实际用户ID的进程总数超过了系统限制CHILD_MAX规定了每个实际用户ID在任一时刻可具有的最大进程使用ulimit–a系统对进程和文件描述符个数的限制。22vforkvfork函数的调用序列和返回值与fork相同,但两者的语义不同。vfork用于创建一个新进程,但是它并不将父进程的地址空间完全复制到子进程中,而是让新进程exec一个新程序vfork保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行。23(3)等待进程结束等待进程结束是指需要一种方法让父进程知道子进程在什么时候结束。由于父进程创建子进程后,两个进程是无序运行的,如果父进程先于子进程结束,那么子进程就会因为找不到父进程的进程号而无法通知父进程,导致资源无法释放,因此需要一种方法让父进程知道子进程在什么时候结束。24等待进程结束当一个进程正常或异常终止时,内核就向其父进程发送SIGCHLD信号。Linux系统提供了waitid()函数,他们的作用是等待另外一个进程的结束。函数定义如下:#includesys/types.h#includesys/wait.hpid_twaitpid(pid_tpid,int*statloc,intoptions);两个函数返回:若成功则为进程ID,若出错则为-125等待进程结束#includesys/types.h#includeunistd.h#includestdio.h#includestdlib.hintmain(){pid_tpid,pid_wait;intstatus;pid=fork();//创建子进程if(-1==pid){//检查是否创建成功printf(Errortocreatenewprocess!\n);return0;}elseif(pid==0){//子进程printf(Childprocess!\n);}else{//父进程printf(Parentprocess!ChildprocessID:%d\n,pid);pid_wait=waitpid(pid,&status,0);//参数0表示,如果没有子进程则立即返回,否则等待指定进程号的子进程printf(Childprocess%dreturned!\n,pid_wait);}return0;}26退出进程Linux提供了几个退出进程相关的函数exit()、_exit()、atexit()和on_exit()。exit()函数的作用是退出当前进程,并且尽可能释放当前进程占用的资源。_exit()函数作用也是退出当前进程,但是并不试图释放进程占用的资源。atexit()函数和on_exit()函数作用都是为程序退出时指定调用用户的代码,区别在于on_exit()函数可以为设定的用户函数设定参数。这几个函数的定义:#includestdlib.hintatexit(void(*function)(void));inton_exit(void(*function)(int,void*),void*arg);voidexit(intstatus);#includeunistd.hvoid_exit(intstatus);27退出进程includestdio.h#includestdlib.h#includeunistd.hvoidbye(void)//退出时回调的函数{printf(Thatwasall,folks\n);}voidbye1(void)//退出时回调的函数{printf(Thisshouldcalledfirst!\n);}intmain(){longa;inti;i=atexit(bye);//设置退出回调函数并检查返回结果if(i!=0){fprintf(stderr,cannotsetexitfunctionbye\n);returnEXIT_FAILURE;}i=atexit(bye1);//设置退出回调函数并检查返回结果if(i!=0){fprintf(stderr,cannotsetexitfunctionbye1\n);returnEXIT_FAILURE;}returnEXIT_SUCCESS;}28常用进程间通信的方法Linux提供了多种进程间通信的方法,常见的包括管道、FIFO、消息队列、信号量、共享存储以及通过socket也可以实现不