Linux下多进程详解

来源:互联网 发布:网络兼职业务员 编辑:程序博客网 时间:2024/06/11 00:10

       进程的经典定义是一个执行中程序的实例。系统中的每个程序都是运行在某个进程上下文中。进程上下文是有程序正确运行所需的状态组成的,这个状态包括存放存储器中的程序代码和数据、栈、通用寄存器内容、程序计数器、环境变量以及打开文件描述符的集合。


一,Linux 进程地址空间

进程为每个程序提供它自己的私有地址空间,通常与这个地址空间关联的存储器字节是不能被其他进程读或者写的,linux进程的地址空间如下图:


二,进程的状态

每个进程在内核中对应一个task_struct结构体,也称为进程描述符,描述进程的所有信息,在linux中进程通常有5种状态。

Linux进程状态:R (TASK_RUNNING),可执行状态。
Linux进程状态:S (TASK_INTERRUPTIBLE),可中断的睡眠状态。
Linux进程状态:D (TASK_UNINTERRUPTIBLE),不可中断的睡眠状态。
Linux进程状态:T (TASK_STOPPED or TASK_TRACED),暂停状态或跟踪状态。
Linux进程状态:Z (TASK_DEAD - EXIT_ZOMBIE),退出状态,进程成为僵尸进程。
Linux进程状态:X (TASK_DEAD - EXIT_DEAD),退出状态,进程即将被销毁。
三,进程控制

1)获取进程ID

[cpp] view plain copy
  1. #include<unistd.h>  
  2. #include<sys/types.h>  
  3. pid_t getpid(void);  
  4. pid_t getppid(void);  
  5. 返回调用者或父进程的PID  

2)创建或终止进程

[cpp] view plain copy
  1. void exit(int status);  
  2. pid_t fork(void);  
  3. pid_t vfork(void);  

exit函数以status退出状态来终止进程。

fork函数创建子进程,新创建的子进程得到父进程虚拟地址空间的一份拷贝,包括文本段、数据段、bss段堆以及用户栈。总之,父进程同子进程有以下特点:并发执行;相同但独立的地址空间;共享文件。fork函数一次调用却返回两次,一次在调用进程中,一次在新创建进程中,在父进程中返回子进程PID,在子进程中返回零。可以通过返回值判断在父进程还是在子进程中,出错返回-1。

vfork与fork一样都创建一个进程,但是它并不是将父进程的地址空间完全复制到子进程中,在子进程调用exec或exit之前,它在父进程的空间中运行。这要就提高了效率。 vfork和fork的另一个区别是:vfork保证子进程先运行,在它调度exex或exit后父进程才可能被调度运行。

3)回收子进程

[cpp] view plain copy
  1. #include <sys/types.h>  
  2. #include <sys/wait.h>  
  3. pid_t wait(int *statloc);  
  4. pid_t waitpid(pid_t pid,int *statloc,int options);  

参数:statloc 指向存放终止状态单元的指针pid    options  控制wait的操作。

两个函数的区别:在一个子进程终止前,wait使其调用者阻塞,而waitpid可以设置成调用者不阻塞。waitpid并不等待第一个终止的子进程----它有若干选项,可以控制他等待的进程。

waitpid并不等待在其调用之后的第一个终止进程,它有若干个选项,可以控制它所等待的进程。

对于waitpid函数的参数pid的解释如下:

  •               pid==-1   等待任一子进程。
  •               pid>0      等待其进程与pid相等的子进程。
  •               pid<-1     等待其组id等于pid绝对值的任一子进程。

options参数使我们能进一步控制waitpid的操作。此参数可以是0,或者是:

  •               WCONTINUED  用于作用控制
  •               WNOHANG          若pid指定的子进程并不是立即可用的,则waitpid不阻塞,此时其返回值为0。
  •               WUNTRACED       用于作业控制。

4)让进程休眠

[cpp] view plain copy
  1. #include<unistd.h>  
  2. unsigned int sleep(unsigned int secs);  
  3. int pause(void);  
sleep返回剩下要休眠的秒数,pause让调用函数休眠,直到进程收到一信号为止。

5)加载并运行程序

execve函数在当前进程上下文中加载并运行一个新程序。函数原型如下:

[cpp] view plain copy
  1. int execl(const char *path,const char *arg,…);  
  2. int execlp(const char *file,const char *arg,…);  
  3. int execle(const char *path,const char *arg,…,char *const envp[]);  
  4. int execv(const char *fath,char *const argv[]);  
  5. int execvp(const char *file,char *const argv[]);  
  6. int execve(const char *file,char *const argv[],char *const envp[]);  

用fork函数创建子进程后,子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程完全由新程序代换,而新程序则从其main函数开始执行。因为调用exec并不创建新进程,所以前后的进程ID并未改变。exec只是用另一个新程序替换了当前进程的正文,数据,堆和栈段。

execve加载file后,调用如下所示的启动代码(具体参考深入理解计算机系统第7章),启动代码准备好栈,并将控制传统给新程序的主函数,该主函数有如下形式。

int main(int argc,char*argv[],char**envp);用户栈组织如下所示。