Skip to content
Published at:

进程控制

进程标识

每个进程者阴一个非负整形表示的唯一进程ID. 虽然唯一,但是是可以复用,延时复用算法:避免防止将新建进程误认为是使用同一ID的某个已终止的先前进程

ID号为1的是init进程,init进程决不会终止,/etc/init或/sbin/init

  • pid_t getpid(void); 调用进程的id
  • pid_t getppid(void); 调用进程的父进程id
  • uid_t getuid(void); 调用进程的实际用户id
  • uid_t geteuid(void); 调用进程的有效用户id
  • gid_t getgid(void); 调用进程的实际组id
  • gid_t getegid(void); 调用进程的有效组id
c
// 对应单词简写:
// p --> process
// p --> parent
// u --> user
// e --> effect
// g --> grou

pid_demo.c

c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

// p --> process
// p --> parent
// u --> user
// e --> effect
// g --> group
int main(int argc, char* argv[]) {
    // pid_t getpid(void); 调用进程的id
    // pid_t getppid(void); 调用进程的父进程id
    // uid_t getuid(void); 调用进程的实际用户id
    // uid_t geteuid(void); 调用进程的有效用户id
    // gid_t getgid(void); 调用进程的实际组id
    // gid_t getegid(void); 调用进程的有效组id

    printf("pid = %d\n", getpid());
    printf("ppid = %d\n", getppid());
    printf("uid = %d\n", getuid());
    printf("euid = %d\n", geteuid());
    printf("gid = %d\n", getgid());
    printf("egid = %d\n", getegid());

    return 0;
}

fork函数

用于从一个已存在的进程中创建一个新进程,新进程称为子进程,原进程称为父进程

  • pid_t fork(void);

正常这个函数会返回两次,一次是父进程调用返回,返回子进程id; 在一次是子进程调用返回,返回0,表示子进程创建成功

fork_demo.c

c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char* argv[]) {

    printf("enter main function\n");

    int ret = fork();

    if (ret == -1) {
        // error
        perror("Failed to create child process\n");
        return ret;
    } else if (ret == 0) {
        // child process;
        printf("success to create child process, pid = %d, parent pid = %d\n", getpid(), getppid());
    } else if (ret > 0) {
        // parent process;
        sleep(1);
        printf("in parent process, child process id is %d, pid = %d, parent id = %d\n", ret, getpid(), getppid());
    }

    printf("exit main function\n");

    return 0;
}

wait/waitpid函数

在每个进程退出的时候,内核释放该进程所有的资源、包括打开的文件、占用的内存等.

一个进程正常或异常终止时,内核就向其父进程发送SIGCHLD信号

父进程可以通过调用wait或waitpid得到它的退出状态同时彻底清除掉这个进程。

wait() 和 waitpid() 函数的功能一样,区别在于,wait() 函数会阻塞,waitpid() 可以设置不阻塞,waitpid() 还可以指定等待哪个子进程结束。

注意:一次wait或waitpid调用只能清理一个子进程,清理多个子进程应使用循环。

  • pid_t wait(int *wstatus);
  • pid_t waitpid(pid_t pid, int *wstatus, int options);

参数wstatus:

是一个传出参数,表示退出信息:是异常退出,还是正常退出

用宏函数去判断:

  • WIFEXITED(status) 为非0 → 进程正常结束
    • WEXITSTATUS(status) 如上宏为真,使用此宏 → 获取进程退出状态 (exit的参数)
  • WIFSIGNALED(status) 为非0 → 进程异常终止
    • WTERMSIG(status) 如上宏为真,使用此宏 → 取得使进程终止的那个信号的编号。
  • WIFSTOPPED(status) 为非0 → 进程处于暂停状态
    • WSTOPSIG(status) 如上宏为真,使用此宏 → 取得使进程暂停的那个信号的编号。
    • WIFCONTINUED(status) 为真 → 进程暂停后已经继续运行

参数options: options 提供了一些额外的选项来控制 waitpid()。

  • 0:同 wait(),阻塞父进程,等待子进程退出。
  • WNOHANG:没有任何已经结束的子进程,则立即返回。
  • WUNTRACED:如果子进程暂停了则此函数马上返回,并且不予以理会子进程的结束状态。(由于涉及到一些跟踪调试方面的知识,加之极少用到)

孤儿进程

父进程运行结束,但子进程还在运行(未运行结束)的子进程就称为孤儿进程(Orphan Process)。

每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为 init ,而 init 进程会循环地 wait() 它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init 进程就会代表党和政府出面处理它的一切善后工作。

因此孤儿进程并不会有什么危害。

僵尸进程

子进程终止,父进程尚未回收,变成僵尸(Zombie)进程。

这样就会导致一个问题,如果进程不调用wait() 或 waitpid() 的话, 那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程,此即为僵尸进程的危害,应当避免。

exec函数族

执行另一个程序

  • int execl(const char *path, const char *arg, ...);
  • int execlp(const char *file, const char *arg, ...);
  • 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[]);
  • int execve(const char *path, char *const argv[], char *const envp[]);
// 单词缩写
// l --> list
// p --> path
// v --> argument vector
// e --> environment

exec_demo.c

c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char* argv[]) {
    // int execl(const char *path, const char *arg, ...);
    // int execlp(const char *file, const char *arg, ...);
    // 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[]);
    // int execve(const char *path, char *const argv[], char *const envp[]);

    // ls -hal ~/code
    pid_t pid = fork();
    if (pid == 0) {
        // child
        execl("/bin/date", "date", "-u", NULL);  // 程序路径, NULL不能少
        execlp("date", "date", "-u", NULL);      // 会去PATH环境变量里去找date
        perror("Failed to exec date ");
    } else if (pid > 0) {
        // parent
        sleep(1);
    }

    return 0;
}

进程调度

基于调度优先级的粗粒度的控制. 调度的策略和调用优先级是由内核确定,

nice值0 ~ (2*NZERO)-1; nice值越小优先级越高;NZERO是系统默认的nice值

  • int nice(int inc); 获取或更改进程的nice值
  • int getpriority(int which, id_t who); 获取进程的nice值
  • int setpriority(int which, id_t who, int prio); 设置进程的nice值

参数which:

  • PRIO_PROCESS:表示进程
  • PRIO_PGRP:表示进程组
  • PRIO_USER:表示用户ID

参数who:

  • 0:表示调用进程,进程组或者用户
  • 当which设置为PRIO_USER并且who为0,使用调用进程的实际用户ID
  • 如果which参数作用于多个进程,则返回所有作用进程中优先级最高的
c
#define NZERO                      20   /* default priority [XSI] */
                                        /* = ((PRIO_MAX - PRIO_MIN) / 2) + 1 */
                                        /* range: 0 - 39 [(2 * NZERO) - 1] */
                                        /* 0 is not actually used */

Note:nice函数中的incr如果太大,系统会直接把它降到最大合法值;如果incr太小,系统会把它提高到最小的合法值

c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <unistd.h>
#if defined(__APPLE__)
#include <sys/syslimits.h>  // NZERO
#elif defined(BSD)
#include <sys/param.h>  // NZERO
#endif

int main(int argc, char* argv[]) {
    int ret;
    // int nice(int inc);
    ret = nice(3);
    printf("nice = %d\n", ret);

    // int getpriority(int which, id_t who);
    int which = PRIO_PROCESS;  // PRIO_PGRP / PRIO_USER
    pid_t pid = getpid();
    ret = getpriority(which, pid);
    printf("nice = %d\n", ret);

    // int setpriority(int which, id_t who, int prio);
    ret = setpriority(which, pid, 100);
    if (ret == -1) {
        printf("Failed to set priority\n");
    } else if (ret == 0) {
        printf("Success to set priority\n");
    }

    ret = getpriority(which, pid);
    printf("after set priority = %d\n", ret);

    return 0;
}