zoukankan      html  css  js  c++  java
  • Linux进程线程学习笔记:进程控制

                                   Linux进程线程学习笔记:进程控制

                                              周银辉 

    创建新进程后得学好如何控制它,至少得知道如何“中止”它,下面介绍几个常用函数。

    1,进程的各个状态 

    为了更好地理解进程控制,我们需要知道进程状态这个概念。和其他普通事物一样,进程始终处于一系列的状态中,比如我们至少可以想象出“运行”,“休眠”之类的。

    TASK_RUNNING :

    可执行状态。这是 “进程正在被CPU执行” 和 “进程正在可执行队列中等待被CPU执行” 统称。也可以将它们拆开成“RUNNING”和“READY”两种状态。

    TASK_INTERRUPTIBLE 和 TASK_UNINTERRUPTIBLE:

    可中断的睡眠状态 和 不可中断的睡眠状态。处于睡眠状态的进程不会被调度到CPU进行执行,而是否可中断的意思是指进程是否会响应异步信号,如果是可中断的,当进程收到某个信号时其会重新回到TASK_RUNNING状态。值得注意的是,如果处于不可中断的睡眠状态时,进程将不响应异步信号,比如你无法“kill -9”

    TASK_STOPPED

    暂停状态。这里的STOPPED是指停止运行(暂停),而不是进程终止。向进程发送SIGSTOP信号可以让进程暂停下来,相反,发送SIGCONT可以将其从TASK_STOPPED状态唤醒而重新进入TASK_RUNNING状态。

    TASK_TRACED

    被跟踪状态。一个进程被另一个进程“TRACE(跟踪)"最经典的例子是DEBUG,比如使用gdb或任何一款ide的debug功能。TASK_TRACED和TASK_STOPPED非常相近,都是让进程暂停下来,区别是不能通过向TASK_TRACED的进程发送SIGCONT信号让其恢复,只能由跟踪该进程的那个进程发送PTRACE_CONT,PTRACE_DETACH等,也就是说得让跟踪进程来决定是否挂起或继续被跟踪进程,当然,跟踪进程如果退出的话,被跟踪进程也会重新回到TASK_RUNNING状态

    TASK_DEAD

    僵尸状态。很搞笑的名字,之所以是“僵尸”而不是“死亡”是因为进程已不响应任何信号以及大部分相关数据已被清除,但其TASK_STRUCT结构仍存在,这个结构相当于进程的“躯壳”,还保留着一些信息,父进程可以利用这些信息得到进程终止前的一些状态。如果你看到某些文档上描写的ZOMBIE也是指的这个状态。

    关于这个状态,可以参考非常有意思的“linux进程状态DZ的处理” (D指的是TASK_UNINTERRUPTIBLE, Z则是指的TASK_DEAD)

    下图描述了进程各个状态之间的相互转化

     

    2, 退出/终止进程

    void _exit(int status) 与 void exit(int status)

    这两个函数都是让进程退出, 参数status表示进程将以何种状态退出,在<stdlib.h>中预定义了一些状态,比如EXIT_SUCCESS(值为0)表示以成功状态退出,EXIT_FAILURE(值为1)表示以失败状态退出。

    调用_exit函数时,其会关闭进程所有的文件描述符,清理内存以及其他一些内核清理函数,但不会刷新流(stdin, stdout, stderr  ...).   exit函数时在_exit函数之上的一个封装,其会调用_exit,并在调用之前先刷新流。

    参考下面这段代码:

    #include <stdio.h>    //for printf(const char *)
    #include <unistd.h>   //for fork()
    #include <sys/wait.h> //for wait(int *)
    #include <stdlib.h>   //for EXIT_SUCCESS


    int main ()
    {
        printf(
    "app start...\n");
        
        
    if(fork() == 0)
        {
            printf(
    "do something in child process ...\n");
            
            exit(EXIT_SUCCESS); 
            
            printf(
    "this will not been executed\n");
        }
        
        
    int status;
        wait(
    &status);
            
        printf(
    "app end\n");
            
        
    return 0;
    }

    上面的代码无论时用exit还是_exit输出结果都如下:

    app start...
    do something in child process ...
    app end

    这是因为stdout缓冲区是按行缓冲的,当遇到换行符时会刷新当前缓冲区,所以当进程退出前即便_exit不刷新,"do somethign in child process "这句话仍然被输出到了屏幕上。

    现在我们将使用不带换行符的printf, 并且也不调用fflush之类的函数,在使用_exit试试:

    #include <stdio.h>    //for printf(const char *)
    #include <unistd.h>   //for fork()
    #include <sys/wait.h> //for wait(int *)
    #include <stdlib.h>   //for EXIT_SUCCESS


    int main ()
    {
        printf(
    "app start...\n");
        
        
    if(fork() == 0)
        {
            printf(
    "do something in child process ...");
            
            _exit(EXIT_SUCCESS); 
            
            printf(
    "this will not been executed\n");
        }
        
        
    int status;
        wait(
    &status);
            
        printf(
    "app end\n");
            
        
    return 0;
    }

    输出结果为:

    app start...
    app end

    如果换成exit则输出结果为:

    app start...
    do something in child process ...app end

    void abort ()

    非正常地退出进程。其会产生一个SIGABORT信号(关于信号,会在下一篇“进程间通信”介绍),然后使进程戛然而止,也就意外着其不会进行清理工作, 但它会刷新缓冲区。 

    #include <stdio.h>    //for printf()
    #include <unistd.h>   //for fork()
    #include <sys/wait.h> //for wait()
    #include <stdlib.h>   //for EXIT_SUCCESS

    int main ()
    {
        printf(
    "app start...\n");
        
        
    if(fork() == 0)
        {
            printf(
    "do something in child process ...");
            
            abort();
            
            printf(
    "this will not been executed\n");
        }
        
        
    int status;
        wait(
    &status);
            
        printf(
    "app end\n");
            
        
    return 0;
    }

    输出为:

    app start...
    do something in child process ...app end

    void atexit( void (*f) () )

    如果想在进程正常结束之前干一点自定义的事情,就可以调用这个函数.  其简单地利用你传入的函数指针执行一个函数回调。

    值得注意的是:其仅仅在调用exit函数结束进程或进程执行完所有代码后自然结束这两种状态下,回调函数才会被执行,也就是说如果进程是被_exit或abort结束的,则atexit函数无效

    #include <stdio.h>    //for printf()
    #include <unistd.h>   //for fork()
    #include <sys/wait.h> //for wait()
    #include <stdlib.h>   //for EXIT_SUCCESS

    void before_exit()
    {
        printf(
    "1,2,3 exit!\n");
    }

    int main ()
    {
        printf(
    "app start...\n");
        
        
    if(fork() == 0)
        {
            printf(
    "do something in child process ...\n");
            
            
    void (*f)() = before_exit;
            atexit(f);
            
            exit(EXIT_SUCCESS);
            
            printf(
    "this will not been executed\n");
        }
        
        
    int status;
        wait(
    &status);
            
        printf(
    "app end\n");
            
        
    return 0;
    }
    输出为:
    app start...
    do something in child process ...
    1,2,3 exit!
    app end

    3,暂停进程

    int pause()

    暂停进程,可以使用pause函数,其会挂起当前进程直到有信号来唤醒或者进程被结束。

    随便提一下,如果你仅仅需要简单地暂停一下(press any key  to continue...), 可以使用 system("pause")这个系统调用,甚至是getch()之类的。 

    关于pause这个函数的Demo和更详细的理解,由于其会涉及到比较多与“信号”相关的知识,所以我打算放到下一篇“进程间通信”来讲

    unsigned sleep(unsigned seconds  

    int usleep(useconds_t useconds)

    int nanosleep(const struct timespec *rqtp, struct timespec *rmtp)

    sleep系列函数都是让进程挂起一段时间,sleep只能精确到秒,usleep能精确到微妙,而nanosleep传说精度更高。 

    4,进程跟踪 

    long ptrace(/*some args*/)

    要像debug程序一样去跟踪进程,是一个比较复杂的问题,接下来我会写一篇“进程跟踪(ptrace)”专门来讲。

    5,waitpid 与 wait(等待子进程结束)

    大家经常看到的关于waitpid的经典例子是:你下载了某个软件的安装程序A,其在安装即将结束时启动了另外一个流氓软件的安装程序B,当B也安装结束后,其告诉你所有安装成功了。A和B分别在不同的进程中,A如何启动B并知道B安装完成了呢?可以很简单地在A中用fork启动B,然后用waitpid(或wait)来等待B的结束。

    pid_t waitpid(pid_t pid, int *stat_loc, int options);

    参数pid:
    如果大于0,表示父进程所需要等待的子进程的进程号

      如果等于0,则表示任意任意group id和父进程相同的子进程

      如果等于-1, 则表示等待任意子进程(有多个子进程时,任意进程结束,函数都会返回),此时waitpid和wait相同。

      如果小于-1,则取其绝对值作为需要等待的子进程的进程号

    参数stat_loc:
    表示进程退出时进程状态的存储位置,有一些专门的宏类根据该位置计算状态值,可以参考这里

    参数options:

      这个参数控制函数是否立即返回,它有三个值:0,WNOHANG(值为1),WUNTRACED(值为2),这三个值多少让有有些迷惑,有个帖子中是如此说的:options的各个常量不是互斥关系,而是通过按位或运算组合起来的关系。进程的状态数是有限的,所有的进程状态改变可能性,是一个元素个数有限的集合,waitpid中指定的子进程的状态改变,必然是这个集合的子集,记为A。options决定如何取A中的元素,默认时(0),只有A不是空集的时候,才会返回,否则阻塞。WNOHANG 告诉waitpid,即使A是空集,也不会挂起,而是立即返回。WUNTRACED 告诉waitpid,如果A中含有进程STOPED状态,也立即返回。如果是被trace的子进程,那么即使不提供WUNTRACED参数,也会理解返回 

    另外,关于waitpid和wait的关系: wait(&status) 等于 waitpid(-1, &status, 0)

    #include <stdio.h>    //for printf()
    #include <unistd.h>   //for fork()
    #include <sys/wait.h> //for wait()
    #include <stdlib.h>   //for EXIT_SUCCESS


    int main ()
    {
        printf(
    "app start...\n");
        
        printf(
    "do something in main process\n");
        
        sleep(
    5);
        
        
    if(fork() == 0)
        {
            printf(
    "do something in child process ...\n");
            
            sleep(
    5);
            
            exit(EXIT_SUCCESS);
            
            printf(
    "this will not been executed\n");
        }
        
        
    int status;
        wait(
    &status);
            
        printf(
    "app end\n");
            
        
    return 0;
    }

    wait的另外一个用途是替子进程“收尸”,这有点难听,但是一个恰当的比喻。我们知道,当进程结束后,进程的大部分资源会被回收,比如释放内存,关闭描述符等,但表示进程的那个结构体STRUCT_TASK却还存在,此时的进程相当于“灵魂已亡,尸体犹在”,所以称之为ZOMBIE状态,这个结构体存在是有它的意义的,因为进程在退出前会讲一些信息保存在其中,父进程可以在wait或waitpid中得到这个结构体并取得相关信息,最后在讲结构体销毁,子进程彻底地消失了。 关于僵尸进程,更多地可以看这里

  • 相关阅读:
    文件输入使System.out.println("程序执行完毕!");这句话的内容输入到文件中
    TI CC2541.h的头文件 for IAR
    状态添加Android游戏开发十日通(4)行走,跳跃,碰撞检测
    命令分析分析企业内连接Exchange 移动设备!
    寄存器数据问题反馈集锦W5200/W5300相关
    发票选择SAP 校验发票时:科目5101140100已设置为与税务不相关
    重写方法Android中的HttpsURLConnection连接
    生成数组C面试题精选
    函数路径Croc Champ 2013 Round 2 题解java教程
    排名中国重读“发展Linux,中日两国之比较”有感java教程
  • 原文地址:https://www.cnblogs.com/zhouyinhui/p/1822594.html
Copyright © 2011-2022 走看看