• 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将会以父进程的身份对僵尸状态的子进程进行处理。

  • 相关阅读:
    获取网络时间,减轻自己服务器的请求压力
    mysql学习记录(windows)
    Docker 部署本地pip源
    npm : 无法加载文件 D:vueProject odejs ode_global pm.ps1
    微信小程序没有找到可构建的npm包
    vue 记录 mode:history 模式 踩过的坑
    Linux学习笔记
    kafka监控 Kafka-eagle-web
    vi 分屏 --(visual 可视模式)
    [安卓网络入门] 获取天气
  • 原文地址:https://www.cnblogs.com/dachao0426/p/9362101.html
Copyright © 2020-2023  润新知