Unix环境——进程管理小结(一)

一、进程的基本概念
    1、进程与程序
    程序是存储在磁盘上的文件,它是包含要执行的机器指令和数据的静态实体。
    进程是一个正在运行的程序,一个程序可能包含多个进程(多任务、多进程),进程在操作系统中是一个执行任务的单位。
    
    2、进程的分类
    交互进程:需要用户输入数据,也会显示一些结果给用户看。
    批处理进程:用来执行脚本的进程,例如Makefil。
    守护进程:它是一种一直活跃的进程,一般是后台的,由操作系统启动时通过开过开机脚本加载或由超级用户加载。

二、在linux下的一些关于进程的命令

ps:显示当用户当前终端所控制的进程。
    -a:显示所有用户的进程
    -x:包括无终端控制的进程
    -u:显示详细信息
    -w:以更宽的方式显示

ps aux   ps aux|grep pid(进程id)

三、进程信息表
    USER:属主
    PID:进程号
    %CPU:cpu占用率
    %MEM:内存使用率
    VSZ:虚拟内存的大小
    RSS:物理内存的使用量
    TTY:终端设备号,如果不是终端控制进程用'?'表示。
    STAT:终端的状态
    START:开始时间
    TIME:运行时间
    COMMAND:开启此进程的命令
    
    四、进程的状态
    O:就绪态,一切准备工作都已经做好,等待被调用。
    R:运行态,Linux下没有就绪态,O也就是R。
    S:可唤醒的睡眠态,系统调用、获取到资源、收到信息都可以被唤醒。
    D:不可唤醒的睡眠态,必须等到的事件发生。
    T:暂停态,收到了SIGSTOP信号,当收到SIGCONT信号则继续运行。
    X:死亡态。
    Z:僵尸态。
    <:高优先级。
    N:低优先级。
    L:多线程进程。
    s:有子进程的进程。
    +:后台进程组。

五、父子进程
        如果进程B是由进程A开启的,那么我们把进程A叫进程B的父进程,进程B叫作进程A的子进程。
        当子进程结束后会向父进程发送,SIGCHLD,父进程收到信号后再支回收子进程。
        当先父进程先结束,子进程就会变成孤儿进程,会被孤儿院(init)收养。
        如果子进程已经结束,但父进程没有及时回收,子进程就变成了僵尸进程。   

六、进程的创建

1、fork函数

    pid_t fork(void);
    功能:创建子进程
   

  1. #include <unistd.h>  
  2. #include <stdio.h>   
  3. int main ()   
  4. {   
  5.     pid_t fpid; //fpid表示fork函数返回的值  
  6.     int count=0;  
  7.     fpid=fork();   
  8.     if (fpid < 0)   
  9.         printf("error in fork!");   
  10.     else if (fpid == 0) {  
  11.         printf("i am the child process, my process id is %d/n",getpid());   
  12.         printf("我是爹的儿子/n");//对某些人来说中文看着更直白。  
  13.         count++;  
  14.     }  
  15.     else {  
  16.         printf("i am the parent process, my process id is %d/n",getpid());   
  17.         printf("我是孩子他爹/n");  
  18.         count++;  
  19.     }  
  20.     printf("统计结果是: %d/n",count);  
  21.     return 0;  
  22. }  

     运行结果是:
    i am the child process, my process id is 5574
    我是爹的儿子
    统计结果是: 1
    i am the parent process, my process id is 5573
    我是孩子他爹
    统计结果是: 1
    在语句fpid=fork()之前,只有一个进程在执行这段代码,但在这条语句之后,就变成两个进程在执行了,这两个进程的几乎完全相同,将要执行的下一条语句都是if(fpid<0)……
    为什么两个进程的fpid不同呢,这与fork函数的特性有关。fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
    1)在父进程中,fork返回新创建子进程的进程ID;
    2)在子进程中,fork返回0;
    3)如果出现错误,fork返回一个负值;

    在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。

************

关于fork()的几点说明

1、失败返回-1,如果成功会返回两次。
2、父进程会返回子进程的id,子进程返回0
3、根据返回值的不同,分别为子进程和父进程设计不同的分支。
4、通过fork创建出的子进程,就是父进程的副本,它会把父进程的堆、栈、全局段、静态数据段、IO流的缓冲区都拷贝一份,父子进程共享代码段。
 5、fork函数调用成功后,父子进程就开始各自执行了,它们的先后顺序是不确定的,但可以通过某些实现来保证。

2、vfork函数
    pid_t vfork(void);
    功能:创建子进程
    
    1、vfork不能单独创建子进程,需要与excl函数簇,配合才完成子进程的创建。
    2、它不会复制父进程的栈、堆、数据、全局等段,也不会共享代码段,而是通过excl函数调用一个程序直接启动,从面提高创建进程的效率。    
    3、使用vfork创建的子进程保证,先执行子进程,后执行父进程。

exec函数簇
 
    exec函数的功能:加载一个可执行文件,要和vfork函数配合才有意义。
    
    int execl(const char *path,  const  char
       *arg, ...);
    path:可执行文件的路径
    arg:给可执行文件的参数,类似于命令行参数,必须以NULL结尾,第一个必须是可以执行文件名。
    execl("","a.out",NULL);
     
   
 
    int  execlp(const char *file, const char
       *arg, ...);
    file:可执行文件的文件名,会从PATH环境变量指定的位置去找可执行文件。
        
    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[]);

例子程序:

#include <stdio.h>
#include <unistd.h>
 
int main(int argc,char* argv[])
{
    printf("我是进程%d ",getpid());
    for(int i=0; i<argc; i++)
    {
        printf("%s ",argv[i]);
    }
 
    while(1)
    {
        sleep(1);
        printf("hello world! ");
    }
}   

#include <stdio.h>
#include <unistd.h>
 
int main()
{
    int pid = vfork();
    if(0 == pid)
    {
        printf("我是进程%d ",getpid());
        sleep(3);
        execl("/home/zhizhen/hello","hello","haha","hehe","你好",NULL);
    }
    printf("我是%d的父进程%d ",pid,getpid());
    while(1)
    {
        printf("大家好,才是真的好! ");
        sleep(1);
    }
}

说明:先有一个hello的程序创建出可执行文件hello 再在另一个程序中使用vfork

注意:

 1、通过exec创建的子进程会替换掉父进程给的代码段,不拷贝父进程的堆、栈、全局、静态数据段,会用新的可执行文件替换掉他们。
 2、exec只是加载一个可执行文件,并不创建进程,不会产生新的进程号。
 3、只有exec函数执行结束(无论成功还是失败),父进程才能继续执行。

七、进程的正常退出
    1、从main退出,在main中执行return 0;
        返回值的低8位会被父进程获取到。
        
    2、系统的_exit(stat)/标准的_Exit(stat),这两个函数几乎没有什么区别。
    #include <unistd.h>
    void _exit(int status);
    #include <stdlib.h>
    void _Exit(int status);
    a、返回值的低8位会被父进程获取到。
    b、使用_exit/_Exit退出前会关闭所有打开的文件流,如果有子进程则会托附给init,然后向父进程发送SIGCHLD信号。
    c、此函数不会返回。
        
    3、调用标准C的exit(stat)函数
    a、exit在底层实现上调用了_exit/_Exit函数,所以_exit/_Exit的特点它都具备。
    b、exit结束前会调用通过atexit/on_exit注册的函数。
    
    int atexit(void (*function)(void));
    int on_exit(void (*function)(int , void *), void *arg);
    
    4、最后一个线程正常结束
    
八、进程异常中止
    1、进程调用了abort函数(段错误、浮点异常)
    2、进程接收到某些信号
        crtl+c
        crtl+
        ctrl+z
    3、最后一个线程收到取消操作,而且线程作出响应。
    
九、子进程的回收
 
    pid_t wait(int *status);
    功能:等待子进程结束,并回收
    
    pid_t waitpid(pid_t  pid,  int  *status,int options);
    功能:等待指定的子进程结束,并回收
     
    1、当一个进程结束时,内核会向它的父进程发送一个信号
        父进程收到这个信号后可以指定一个函数来处理,也可以选择忽略,默认情况下是忽略的。
        
    2、父进程调用wait调用才是有意义
        如果所有的子进程都在运行,则父进程阻塞(wait)
        只要有一个子进程结束了,会立即返回子进程的id和结束状态。
        当所有子进程都结束运行时,wait会返回-1。
        如果在调用wait之前子进程就已经结束(僵尸子进程),执行wait函数时会立即返回并回收僵尸进程。
        
    3、waitpid函数可以指定等于哪个子进程
        pid:指定的pid
            == -1 功能与wait类似,pid就无意义了。
            > 0 等待进程号是pid的进程结束。
            
            == 0 等待组id等于pid的进程组中任意进程结束。
            < -1 等待组id是pid的绝对值的进程组中任意进程结束。
            
        status:用于接收子进程的结束状态,如果不需要状态码可以设置为NULL;
        
        options:
            0 以阻塞状态等待子进程结束
            WNOHANG 如果没有子进程退出会立即返回。
            WUNTRACED 等待的进程处于停止状态,并且之前没有报告过,则立即返回。
            
    4、如果不调用wait/waitpid函数,子进程结束后就处于僵尸状态,当父进程也结束时,父进程的父进程会把他们统一回收。

最后关于僵尸进程和子进程的一些理解:

孤儿进程是没有父进程的进程,孤儿进程这个重任就落到了init进程身上,init进程就好像是一个民政局,专门负责处理孤儿进程的善后工作。每当出现一个孤儿进程的时候,内核就把孤 儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init进程就会代表党和政府出面处理它的一切善后工作。因此孤儿进程并不会有什么危害。

  任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。这是每个 子进程在结束时都要经过的阶段。如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“Z”。如果父进程能及时 处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。  如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。

原文地址:https://www.cnblogs.com/dachao0426/p/9362101.html