• 【进程】进程创建,执行与控制


    进程创建

    -->进程表

      系统用一个叫做“进程表”的东西来维护系统中的进程,进程表中的一个条目维护着存储着一个进程的相关信息,比如进程号,进程状态,寄存器值等等...
      当分配给进程A的“时间片”使用完时,CPU会进行上下文切换以便运行其他进程,比如进程B,这里所谓的“上下文切换”,主要就是在操作那个“进程表”,其将进程A的相关信息(上下文)保存到其对应的进程表项中, 与之相反,其会从对应于进程B的进程表项中读取相关信息并运行之。
      那么,如果进程A新建了一个进程C呢?进程表会多这样一个表项,并且该表项拥有一个唯一的ID,也就是进程号(PID),进程表项的其他值大部分与进程A的相同,具体说来,就是C和A共享代码段,并且C将A的数据空间,堆栈等复制一份 ,然后从A创建C的地方开始运行。

    -->fork()函数

    pid_t fork(void);

      其包含在 unistd.h 头文件中,

    1. 其中pid_t是表示“type of process id”的32位整数,
    2. 至于函数的返回值,取决于在哪个进程中来检测该值,如果是在新创建的进程中,其为0;如果是在父进程中(创建新进程的进程),其为新创建的进程的id; 如果创建失败,则返回负值。

      那么如何创建一个新进程以运行一个新程序,那就会乃至exec函数,它们两者相结合就可以了~


    进程执行

    -->执行程序

      当启动一个新进程以后,新进程会复制父进程的大部份上下文并接着运行父进程中的代码,如果我们使新进程不运行原父进程的代码,转而运行另外一个程序集中的代码,这就相当于启动了一个新程序。这里的代码我们可以理解成一个可执行程序。
      所以,要运行一个新程序,需要最基本的两步:

    1. 创建一个可运行程序的环境,也就是进程。
    2. 将环境中的内容替换成你所希望的,也就是用你希望运行的可执行文件去覆盖新进程中的原有映像,并从该可执行文件的起始处开始执行。

      要做到第一点,非常简单,fork函数就可以,要做到第二点,则可以利用exec函数族。
      exec是一族函数的简称,包含在<unistd.h>中它们作用都一样,用一个可执行文件覆盖进程的现有映像,并转到该可执行文件的起始处开始执行。
      

      原型如下:

    1 int execl(const char *path, const char *arg0, ... /*, (char *)0 */); 
    2 int execlp(const char *file, const char *arg0, ... /*, (char *)0 */); 
    3 int execle(const char *path, const char *arg0, ... /*, (char *)0, char *const envp[]*/); 
    4 int execv(const char *path, char *const argv[]); 
    5 int execvp(const char *file, char *const argv[]); 
    6 int execve(const char *path, char *const argv[], char *const envp[]);

      名字这么相近的函数,感觉好容易混淆,那就从l,v,p,e 这样的后缀来区分:

    • l:参数为一个逗号分隔的参数列表,并以char* 0作为列表结尾(NULL)
    • v: 参数为字符串数组,数组的最后一个元素为char* 0
    • p: 可以通过系统环境变量查找文件位置
    • e:调用者显示传入环境变量

      值得注意的是

    1. arg0是可执行文件本身.
    2. 最后有一个注释/*, (char*)0 */是提醒我们最后一个参数应该传空字符串。
    3. 如果函数运行成功,则不会有任何返回值,否则返回-1,而具体的错误号会被设置在errno,errno是一个全局变量,用于程序设置错误号,跟win32的getLastError函数类似。
    4. 注意exec是"覆盖"进程的现有映像(新进程执行结束后便直接退出了).

    进程控制

     

    -->wait函数

      wait函数将当前进程休眠,直到该进程的某个子进程结束或者有特定的信号来唤醒。如果子进程正常结束,则讲子进程的进程id(pid)作为返回值,发生错误则返回-1,而status参数讲传出子进程的结束状态值。
      原型如下

    pid_t wait (int * status); //包含在 <sys/wait.h> 中

    -->进程的各个状态

      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也是指的这个状态。

     

    -->退出/终止进程

    1 void _exit(int status)
    2 void exit(int status)

      这两个函数都是让进程退出.

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

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

    void abort ()

      非正常地退出进程。其会产生一个SIGABORT信号,然后使进程戛然而止,也就意味着其不会进行清理工作, 但它会刷新缓冲区。

    void atexit( void (*f) () )

      如果想在进程正常结束之前干一点自定义的事情,就可以调用这个函数. 其简单地利用你传入的函数指针执行一个函数回调。
      值得注意的是:其仅仅在调用exit函数结束进程或进程执行完所有代码后自然结束这两种状态下,回调函数才会被执行,也就是说如果进程是被_exit或abort结束的,则atexit函数无效


    -->暂停进程

    int pause()

      暂停进程,可以使用pause函数,其会挂起当前进程直到有信号来唤醒或者进程被结束。
      如果你仅仅需要简单地暂停一下(press any key to continue...), 可以使用 system("pause")这个系统调用,甚至是getch()之类的。

    unsigned sleep(unsigned seconds) 

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

     

    -->进程跟踪

    long ptrace(/*some args*/)

      要像debug程序一样去跟踪进程,是一个比较复杂的问题。

    -->waitpid 与 wait(等待子进程结束)

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

     

      参数pid:

    • 如果大于0,表示父进程所需要等待的子进程的进程号
    • 如果等于0,则表示任意任意group id和父进程相同的子进程
    • 如果等于-1, 则表示等待任意子进程(有多个子进程时,任意进程结束,函数都会返回),此时waitpid和wait相同。
    • 如果小于-1,则取其绝对值作为需要等待的子进程的进程号

      参数stat_loc:
      表示进程退出时进程状态的存储位置,有一些专门的宏类根据该位置计算状态值
      另外,waitpid和wait的关系: wait(&status) 等于 waitpid(-1, &status, 0)

  • 相关阅读:
    获取最外层View
    Activity的lanuchmode
    decorview that was originally added here or java.lang.IllegalArgumentException: View not attached to window manager
    Android开源项目
    Android屏幕适配
    android获取根视图
    Nginx 安装 和 特性介绍
    kubernetes Pod控制器
    kubernetes 资源清单定义入门
    kubernetes 应用快速入门
  • 原文地址:https://www.cnblogs.com/lcw/p/3235749.html
Copyright © 2020-2023  润新知