POSIX 进程 API

更新时间:
2024-03-14
下载文档

POSIX 进程 API

SylixOS 提供了一套 POSIX 兼容 API,因此可以方便地编写 SylixOS 程序或移植程序到 SylixOS。

除了在 SylixOS Shell 中执行程序创建进程外,SylixOS 也提供了在程序中创建进程以及设置进程参数的 API。当一个进程创建另外一个进程时,本进程成为被创建进程的父进程,被创建进程则成为本进程的子进程。父子进程相互关联,可以调用相应的 API 查找到对方,而在子进程退出时,子进程会发送信号通知父进程,此时父进程可以获取子进程退出码,回收子进程资源。如果一个进程的父进程先于子进程退出,则子进程将成为孤儿进程,孤儿进程的资源回收工作在其退出时由系统自动完成。

执行程序

使用 exec 函数执行程序

#include <process.h>
int execl(const char *path, const char *argv0, ...);
int execle(const char *path, const char *argv0, ... /* char *const *envp */);
int execlp(const char *file, const char *argv0, ...);
int execv(const char *path, char * const *argv);
int execve(const char *path, char * const *argv, char * const *envp);
int execvp(const char *file, char * const *argv);
int execvpe(const char *file, char * const *argv, char * const *envp);

以上函数原型分析:

  • 返回函数执行结果,成功返回 0,失败返回-1 并设置错误码。
  • 参数 path 是可执行文件路径。
  • 参数 argv0 是第一个命令行参数,一般情况下第一个命令行参数为命令名称。
  • 参数 file 是可执行文件名,与参数 path 的区别是不带目录,应用程序加载器在指定路径搜索该文件。SylixOS 中应用程序动态库的搜索路径依次如下:
    • Shell 当前目录(通常为用户主目录),注意不是应用程序所在目录。
    • PATH 环境中包含的搜索路径。
  • 参数 ... 为可变参数,表示命令行中剩余的参数,命令行参数以 0 结束。在 execle 函数中,在以 0 结束的命令行参数后还有一个环境变量数组,数组以 0 结束,参考 envp 参数说明。
  • 参数 argv 是由命令行参数组成的字符串数组,数组以可执行文件名开始,以 0 结束。为 0 表示不使用命令行参数。
  • 参数 envp 是在执行程序前需要预先设置的进程环境变量字符串数组,数组以 0 结束。为 0 表示不需要环境变量设置。

注意:
本函数只能由当前进程主线程调用,否则会返回失败。

下面实例展示了 exec 系列函数的用法,以下三段程序依次展示了 execl、execle、execve 函数的使用方法。

#include <stdio.h>
#include <stdlib.h>

int main (int argc, char *argv[])
{
    char *env_parent = (char*)0;
    if (argc < 2) {
        printf("child process param error!\n");
        return  (-1);
    }
    printf("child process %s\n", argv[1]);
    env_parent = getenv("PARENT");
    if (env_parent) {
        printf("environment variable PARENT = %s\n", env_parent);
    }
    return  (0);
}
#include <stdio.h>
#include <process.h>

int main (int argc, char *argv[])
{
    printf("before execl\n");
    execl("./child_process", 
          "child_process", 
          "execl test", (char *)0);
    printf("after execl\n");
    return 0;
}

在 SylixOS Shell 中执行程序:

# ./execl
before execl
child process, execl test
#include <stdio.h>
#include <process.h>
int main (int argc, char *argv[])
{
    char *env[] = { "PARENT=execle_demo", (char *)0 };
    printf("before execle\n");
    execle("./child_process", "child_process",
           "execle demo", (char *)0, env);
    printf("after execle\n");
    return 0;
}

在 SylixOS Shell 中执行程序:

# ./execle
before execle
child process, execle demo
environment variable PARENT = execle_demo
#include <stdio.h>
#include <process.h>
int main (int argc, char *argv[])
{
    char *cmd[] = { "child_process", "execve test", (char *)0 };
    char *env[] = { "PARENT=execve_demo", (char *)0 };
    printf("before execve\n");
    execve("./child_process", cmd, env);
    printf("after execve\n");
    return 0;
}

在 SylixOS Shell 中执行程序:

# ./execve
before execve
child process, execve test
environment variable PARENT = execve_demo

由实例可见,exec 函数会覆盖本进程环境,所以 exec 函数后面的打印语句不会被执行。

创建进程

使用 posix_spawn 函数创建进程

posix_spawn 系列函数功能比 exec 系列函数强大,使用也更为复杂。posix_spawn 除了能设置新进程命令行参数和环境变量外,还可以设置新进程文件操作和进程属性。函数原型如下:

#include <spawn.h>
int posix_spawn(pid_t *pid, const char              *path,
                const posix_spawn_file_actions_t    *file_actions,
                const posix_spawnattr_t             *attrp, 
                char *const                          argv[],
                char *const                          envp[]); 
int posix_spawnp(pid_t *pid, const char             *file,
                 const posix_spawn_file_actions_t   *file_actions,
                 const posix_spawnattr_t            *attrp,
                 char *const                         argv[], 
                 char *const                         envp[]); 

以上函数原型分析:

  • 此函数成功返回 0,失败返回-1 并设置错误码。
  • 参数 pid 保存新进程 id。
  • 参数 path 是可执行文件路径。
  • 参数 file 是可执行文件名,与参数 path 的区别是不带目录,应用程序加载器在指定路径搜索该文件。SylixOS 中应用程序动态库的搜索路径依次如下:
    • Shell 当前目录,注意不是应用程序所在目录;
    • PATH 环境中包含的搜索路径。
  • 参数 file_actions 是新进程启动时需要处理的文件操作集,为 0 表示不进行任何文件操作。对 file_actions 的构造和赋值等操作在后续函数中介绍,在本章后面的介绍中将其命名为文件操作集对象。
  • 参数 attrp 是新进程初始化属性,为 NULL 表示不设置。对 attrp 的构造和赋值等操作在后续函数中介绍,在本章后面的介绍中将其命名为进程属性对象。
  • 参数 argv 是由命令行参数组成的字符串数组,数组以可执行文件名开始,以 NULL 结束。为 NULL 表示不使用命令行参数。
  • 参数 envp 是需要预先设置的进程环境变量字符串数组,数组以 NULL 结束。为 NULL 表示不需要设置环境变量。

初始化进程属性对象

#include <spawn.h>
int posix_spawnattr_init(posix_spawnattr_t *attrp);

函数 posix_spawnattr_init 原型分析:

  • 此函数时成功返回 0,失败时返回错误码。
  • 参数 attrp 是需要初始化的进程属性对象。

销毁进程属性对象

#include <spawn.h>
int posix_spawnattr_destroy(posix_spawnattr_t *attrp); 

函数 posix_spawnattr_destroy 原型分析:

  • 此函数成功时返回 0,失败时返回错误码。
  • 参数 attrp 是需要销毁的进程属性对象。

设置进程属性对象中的工作目录

#include <spawn.h>
int posix_spawnattr_setwd(posix_spawnattr_t *attrp, const char *pwd);

函数 posix_spawnattr_setwd 原型分析:

  • 此函数成功返回 0,失败返回错误码。
  • 参数 attrp 是进程属性对象。
  • 参数 pwd 是新工作目录字符串。

获取进程属性对象中的工作目录

#include <spawn.h>
int posix_spawnattr_getwd(const posix_spawnattr_t *attrp, 
char *pwd,   size_t          size);

函数 posix_spawnattr_getwd 原型分析:

  • 此函数成功返回 0,失败返回错误码。
  • 参数 attrp 是进程属性对象。
  • 参数 pwd 是缓冲区,用于保存工作目录字符串。
  • 参数 size 是缓冲区长度。

设置进程属性对象中的信号掩码

#include <spawn.h>
int posix_spawnattr_setsigmask(posix_spawnattr_t    *attrp,
                               const sigset_t       *sigmask);

函数 posix_spawnattr_setsigmask 原型分析:

  • 此函数成功返回 0,失败返回错误码。
  • 参数 attrp 是进程属性对象。
  • 参数 sigmask 是需要设置的进程信号掩码。

获取进程属性对象中的信号掩码

#include <spawn.h>
int posix_spawnattr_getsigmask(const posix_spawnattr_t      *attrp,
                               sigset_t                     *sigmask);

函数 posix_spawnattr_getsigmask 原型分析:

  • 此函数成功返回 0,失败返回错误码。
  • 参数 attrp 是进程属性对象。
  • 参数 sigmask 是获取到的进程信号掩码。

设置进程属性对象中的标记位

#include <spawn.h>
int posix_spawnattr_setflags(posix_spawnattr_t *attrp,
                             short              flags); 

函数 posix_spawnattr_setflags 原型分析:

  • 此函数成功返回 0,失败返回错误码。
  • 参数 attrp 是进程属性对象。
  • 参数 flags 是进程属性标记位掩码,只有在标记位中使能的属性才会在进程启动时生效,标记位掩码值为以下值的任意组合。
宏名解释
POSIX_SPAWN_SETPGROUP使能进程组设置
POSIX_SPAWN_SETSIGMASK使能信号掩码设置
POSIX_SPAWN_SETSCHEDULER使能调度器参数设置
POSIX_SPAWN_SETSCHEDPARAM使能进程优先级设置

获取进程属性对象中的标记位

#include <spawn.h>
int posix_spawnattr_getflags(const posix_spawnattr_t   *attrp,
                             short                     *flags);

函数 posix_spawnattr_getflags 原型分析:

  • 此函数成功返回 0,失败返回错误码。
  • 参数 attrp 是进程属性对象。
  • 参数 flags 是进程属性标记位掩码,只有在标记位中使能的属性才会在进程启动时生效,标记位的值如下表所示。
宏名解释
POSIX_SPAWN_SETPGROUP使能进程组设置
POSIX_SPAWN_SETSIGMASK使能信号掩码设置
POSIX_SPAWN_SETSCHEDULER使能调度器参数设置
POSIX_SPAWN_SETSCHEDPARAM使能进程优先级设置

设置进程组号到进程属性对象

#include <spawn.h>
int posix_spawnattr_setpgroup(posix_spawnattr_t     *attrp, 
                              pid_t                  pgroup); 

函数 posix_spawnattr_setpgroup 原型分析:

  • 此函数成功返回 0,失败返回错误码。
  • 参数 attrp 是进程属性对象。
  • 参数 pgroup 是进程组号。

从进程属性对象获取进程组号

#include <spawn.h>
int posix_spawnattr_getpgroup(const posix_spawnattr_t *attrp, 
                              pid_t                   *pgroup);

函数 posix_spawnattr_getpgroup 原型分析:

  • 此函数成功返回 0,失败返回错误码。
  • 参数 attrp 是进程属性对象。
  • 参数 pgroup 是返回的进程组号。

设置进程属性对象中的调度策略

#include <spawn.h>
int posix_spawnattr_setschedpolicy(posix_spawnattr_t *attrp,
                                   int                schedpolicy); 

函数 posix_spawnattr_setschedpolicy 原型分析:

  • 此函数成功返回 0,失败返回错误码。
  • 参数 attrp 是进程属性对象。
  • 参数 schedpolicy 是进程调度策略。schedpolicy 可以有以下几种情况:。
宏名解释
LW_OPTION_SCHED_FIFOSCHED_FIFO 先到先服务实时调度策略
LW_OPTION_SCHED_RRSCHED_RR 时间片轮转实时调度策略

获取进程属性对象中的调度策略

#include <spawn.h>
int posix_spawnattr_getschedpolicy(const posix_spawnattr_t *attrp,
                                   int                     *schedpolicy);

函数 posix_spawnattr_getschedpolicy 原型分析:

  • 此函数成功返回 0,失败返回错误码。
  • 参数 attrp 是进程属性对象。
  • 参数 schedpolicy 是获取到的进程调度策略。

设置进程属性对象中的进程优先级

#include <spawn.h>
int posix_spawnattr_setschedparam(posix_spawnattr_t        *attrp,
                                  const struct sched_param *schedparam);                        

函数 posix_spawnattr_setschedparam 原型分析:

  • 此函数成功返回 0,失败返回错误码。
  • 参数 attrp 是进程属性对象。
  • 参数 schedparam** 是调度参数。

获取进程属性对象中的进程优先级

#include <spawn.h>
int posix_spawnattr_getschedparam(const posix_spawnattr_t  *attrp,
                                  struct sched_param       *schedparam);

函数 posix_spawnattr_getschedparam 原型分析:

  • 此函数成功返回 0,失败返回错误码。
  • 参数 attrp 是进程属性对象。
  • 参数 schedparam 是获取到的进程优先级设置参数。

初始化文件操作集对象

#include <spawn.h>
int posix_spawn_file_actions_init(posix_spawn_file_actions_t *file_actions);

函数 posix_spawn_file_actions_init 原型分析:

  • 此函数成功返回 0,失败返回错误码。
  • 参数 file_actions 是文件操作集对象。

销毁文件操作集对象

#include <spawn.h>
int posix_spawn_file_actions_destroy(posix_spawn_file_actions *file_actions);

函数 posix_spawn_file_actions_destroy 原型分析:

  • 此函数成功返回 0,失败返回错误码。
  • 参数 file_actions 是文件操作集对象。

添加打开文件操作到文件操作集对象

#include <spawn.h>
int posix_spawn_file_actions_addopen(posix_spawn_file_actions_t  *file_actions,
                                     int                          fd, 
                                     const char                  *path, 
                                     int                          oflag, 
                                     mode_t                       mode);

函数 posix_spawn_file_actions_addopen 原型分析:

  • 此函数成功时返回 0,失败时返回错误码。
  • 参数 file_actions 是文件操作集对象。
  • 参数 fd 是文件打开后的文件号。
  • 参数 path 是文件路径。
  • 参数 oflag 是文件打开方式。
  • 参数 mode 只在新建文件时有效,表示新文件的创建模式。

添加文件关闭操作到文件操作集对象

#include <spawn.h>
int posix_spawn_file_actions_addclose(posix_spawn_file_actions_t    *file_actions,
                                      int                            fd);

函数 posix_spawn_file_actions_addclose 原型分析:

  • 此函数成功返回 0,失败返回错误码。
  • 参数 file_actions 是文件操作集对象。
  • 参数 fd 是需要关闭的文件号。

添加文件描述符 dup 操作到文件操作集对象

#include <spawn.h>
int  posix_spawn_file_actions_adddup2(posix_spawn_file_actions_t    *file_actions,
                                      int                            fd, 
                                      int                            newfd);

函数 posix_spawn_file_actions_adddup2 原型分析:

  • 此函数成功时返回 0,失败返回时错误码。
  • 参数 file_actions 是文件操作集对象。
  • 参数 fd 是需要复制的文件号。
  • 参数 newfd 是指向参数 fd 对应文件的新文件号。

下面通过实例说明使用 POSIX API 创建进程的流程。

#include <stdio.h>
#include <spawn.h>
int main (int argc, char *argv[])
{
    posix_spawn_file_actions_t  file_actions;
    struct sched_param          schedparam;
    posix_spawnattr_t           spawnattr;
    pid_t                       pid;

    char      *cmd[]      = { "child_process", "execve test", (char *)0 };
    char      *env[]      = { "PARENT=execve_demo", (char *)0 };
    char     *log_file    = "/tmp/child_process_output";
    mode_t    mode        = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;

   /* 
    * 初始化进程属性对象
    */
    if (posix_spawnattr_init(&spawnattr) != 0) {
            fprintf(stderr, "init posix_spawnattr_t failed\n");
            return  (-1);
}
    /* 
    * 新建进程优先级为 NORMAL
    */
    schedparam.sched_priority = PX_PRIORITY_CONVERT(LW_PRIO_NORMAL);    
    posix_spawnattr_setschedparam(&spawnattr, &schedparam);
    posix_spawnattr_setflags(&spawnattr, POSIX_SPAWN_SETSCHEDPARAM);
    /* 
    * 初始化文件操作对象
    */
    if (posix_spawn_file_actions_init(&file_actions) != 0) {        
        fprintf(stderr, "init posix_spawn_file_actions_t failed\n");
        return  (-2);
    }
    /*
     * 关闭标准输入、标准输出、错误输出
     */
    if (posix_spawn_file_actions_addclose(&file_actions, STDIN_FILENO)  != 0 ||
        posix_spawn_file_actions_addclose(&file_actions, STDOUT_FILENO) != 0 ||
        posix_spawn_file_actions_addclose(&file_actions, STDERR_FILENO) != 0) {
        fprintf (stderr, "close std file failed\n");
        return  (-3);
    }
    /*
     * 重定向标准输出到log文件
     */
    if (posix_spawn_file_actions_addopen(&file_actions, 
                                         STDOUT_FILENO, 
                                         log_file,
                                         O_WRONLY | O_CREAT | O_TRUNC, 
                                         mode) != 0) {
        fprintf (stderr, "redirection std output failed\n");
        return  (-4);
    }
    if (posix_spawnp(&pid, "./child_process",
                     &file_actions, 
                     &spawnattr, cmd, env) != 0) {         /* 启动进程            */
        posix_spawnattr_destroy(&spawnattr);
        posix_spawn_file_actions_destroy(&file_actions);
        return  (-6);
    }
    posix_spawnattr_destroy(&spawnattr);
    posix_spawn_file_actions_destroy(&file_actions);
    return  (0);
}

本实例使用以下程序作为子进程可执行文件,在 SylixOS Shell 中执行程序:

# ./Function_posix_spawn
#

因为在进程启动时已经重定向了标准输出,所以 Shell 中看不到任何程序输出,查看/tmp/child_process_output 文件中的内容可以看到程序输出。

# cat /tmp/child_process_output
child process, execve test
environment variable PARENT = execve_demo

进程调度

进程是系统资源分配的基本单位(可以看成是资源的容器),线程是调度的基本单位,因此 SylixOS 的进程调度是指对进程的主线程进行调度。

设置进程调度优先级

设置满足条件的所有线程的 SylixOS 调度优先级。

#include <sys/resource.h>
int setpriority(int which, id_t who, int value);

函数 setpriority 原型分析:

  • 此函数成功时返回 0,失败时返回-1 并设置错误号。
  • 参数 which 指定参数 who 的意义。
  • 参数 who 的意义由参数 which 指定,如下表所示。
宏名解释
PRIO_PROCESS参数 who 的值为进程 ID
PRIO_PGRP参数 who 的值为组 ID
PRIO_USER参数 who 的值为用户 ID
  • 参数 value 是要设置的 SylixOS 优先级。

获取 SylixOS 调度优先级

#include <sys/resource.h>
int getpriority(int which, id_t who);

函数 getpriority 原型分析:

  • 返回满足条件的所有线程中的 SylixOS 优先级最大值,注意,这里是优先级值最大,因此优先级最低。
  • 参数 which 指定参数 who 的意义,如上表所示。
  • 参数 who 的意义由参数 which 指定。

setpriority 函数和 getpriority 函数设置和获取 SylixOS 优先级,与前面 sched_param 结构体中 sched_priority 成员所指示的优先级不同,sched_priority 成员是 POSIX 优先级。

调整 SylixOS 调度优先级

nice 函数可以调整当前进程优先级。

#include <unistd.h>
int nice(int incr);

函数 nice 原型分析:

  • 此函数成功返回 0,失败返回-1 并设置错误号。
  • 参数 incr 是要调整的数值。本函数对 incr 参数的处理流程如下:
    • 首先获取当前进程中所有线程中的最低优先级,即数值最大的的优先级。
    • 然后将获取到的值和 incr 参数求和。
    • 将上一步求和的结果设置到当前进程的所有线程中。

下面实例说明如何使用 POSIX 进程调度 API 设置和获取进程优先级。

#include <stdio.h>
#include <unistd.h>
#include <sys/resource.h>
int main (int argc, char *argv[])
{
    int         pid;
    int         priority;

    pid = getpid();                                     /* 获取进程ID             */
    priority     = getpriority(PRIO_PROCESS, pid);      /* 获取进程优先级         */
    fprintf(stdout, "child process priority: %d\n", priority);
    sleep(3);                                           /* 等待父进程修改优先级   */
    priority     = getpriority(PRIO_PROCESS, pid);      /* 获取修改后的优先级     */
    fprintf(stdout, "child process priority after sched_setscheduler: %d\n", priority);
    nice(1);                                            /* 使用nice调整优先级     */
    priority     = getpriority(PRIO_PROCESS, pid);      /* 获取调整后的优先级     */
    fprintf(stdout, "child process priority after nice: %d\n", priority);
    return (0);
}
#include <stdio.h>
#include <spawn.h>
#include <sched.h>

int main (int argc, char *argv[])
{
    pid_t                    pid;
    struct sched_param       param;
    int                      policy;
    char                    *policy_name[] = {"SCHED_RR", 
                                              "LW_OPTION_SCHED_FIFO"};

    if (posix_spawnp(&pid, "./sched_child_proc",
                     NULL, NULL, NULL, NULL) != 0) {     /* 启动进程              */
        fprintf(stderr, "create child process failed\n");
        return  (-1);
    }
    sleep(1);                                            /* 等待子进程启动        */
    if (sched_getparam(pid, &param) != 0) {              /* 获取子进程优先级      */
        fprintf(stderr, "get sched_param failed\n");
        return (-3);
    }
    policy = sched_getscheduler(pid);                    /* 获取进程调度策略      */
    if (policy == -1) {
        fprintf(stderr, "get scheduler policy failed\n");
        return (-4);
    }
    fprintf(stdout, "child process pid:%d, posix priority:%d, policy:%s\n",
            pid, param.sched_priority, policy_name[policy]);
    param.sched_priority += 1;
    if (sched_setscheduler(pid, policy, &param) != 0) {  /* 设置进程优先级及调度策略 */
        fprintf(stderr, "get sched_param and scheduler policy failed\n");
        return (-5);
    }
    fprintf(stdout, "set posix priority to %d\n", param.sched_priority);
    sleep(5);                                            /* 等待子进程结束         */
    return  (0);
}

实例中父进程使用 sched_getparam 函数获取子进程优先级,子进程使用 getpriority 函数获取自身优先级,由此可以看出 POSIX 优先级和 SylixOS 优先级之间的转换关系。

在 SylixOS Shell 中执行程序:

# ./Parent_Process_Scheduler
child process priority: 200
child process pid:18, posix priority:55, policy:SCHED_RR
set posix priority to 56
child process priority after sched_setscheduler: 199
child process priority after nice: 200

设置进程亲和度

sched_setaffinity 函数亲和指定进程内所有线程在指定 cpu 集上运行,该函数只用于多核情况。

#include <sys/resource.h>
int sched_setaffinity(pid_t pid, size_t setsize, const cpu_set_t *set);

函数 sched_setaffinity 原型分析:

  • 此函数成功返回 0,失败返回-1 并设置错误码。
  • 参数 pid 指定进程 ID。
  • 参数 set 指定允许进程执行的处理器核,它是一个 2048 位的位集合,每个位代表一个处理器核,如果为 1 表示允许进程在该核上执行,否则表示不允许。

获取进程主线程亲和度

sched_getaffinity 函数获取指定进程的主线程 CPU 亲和度的设置情况。

#include <sys/resource.h>
int sched_getaffinity(pid_t pid, size_t setsize, cpu_set_t *set);

函数 sched_getaffinity 原型分析:

  • 此函数成功返回 0,失败返回-1 并设置错误号。
  • 参数 pid 指定进程 ID。
  • 参数 set 表示允许进程执行的处理器核,它是一个 2048 位的位集合,每个位代表一个处理器核,如果为 1 表示允许进程在该核上执行,否则表示不允许。

进程关系

获取进程 ID

#include <unistd.h>
pid_t getpid(void);

函数 getpid 原型分析:

  • 此函数返回调用进程 ID。

设置进程组 ID

#include <unistd.h>
int setpgid(pid_t pid, pid_t pgid);

函数 setpgid 原型分析:

  • 此函数成功返回 0,失败返回-1 并设置错误码。
  • 参数 pid 是目标进程 ID。
  • 参数 pgid 是需要设置的进程组 ID。

获取进程组 ID

#include <unistd.h>
pid_t getpgid(pid_t pid);

函数 getpgid 原型分析:

  • 此函数成功返回目标进程组 ID,失败返回-1 并设置错误码。
  • 参数 pid 是进程 ID。

设置进程组 ID

#include <unistd.h>
pid_t setpgrp(void);

函数 setpgrp 原型分析:

  • 此函数将调用进程组 ID 设置为本进程 ID,使本进程成为会话头,成功返回 0,失败返回-1 并设置错误码。

获取进程组 ID

#include <unistd.h>
pid_t getpgrp(void);

函数 getpgrp 原型分析:

  • 此函数返回调用进程组 ID。

获取父进程 ID

#include <unistd.h>
pid_t getppid(void);

函数 getppid 原型分析:

  • 此函数返回调用进程父进程 ID。

每个 SylixOS 进程包含三个用户 ID:

  • 实际用户 ID:实际用户 ID 是启动进程的用户 ID,一般为启动进程的 Shell 登录用户 ID。
  • 有效用户 ID:有效用户 ID 是进程当前正在使用的用户 ID,如果需要权限判断,内核只会校验有效用户 ID。
  • 保存的设置用户 ID:保存的设置用户 ID 是进程可执行文件的所属用户 ID,保存的设置用户 ID 只有当可执行文件设置了 S_ISUID 位才有效。

同理,用户组 ID 也分为实际用户组 ID,有效用户组 ID 和保存的设置用户组 ID。在进程启动时,如果文件设置了 S_ISUID 属性位,则将进程有效用户 ID 和保存的设置用户 ID 设置为文件的拥有者 ID,如果没有设置 S_ISUID 位,则保存的设置用户 ID 无效,有效用户 ID 被设置为实际用户 ID。进程组 ID 也做同样设置,不同的是检测的文件属性位为 S_ISGID 位。

以下是进程用户 ID 的设置和获取方法。

S_ISUID 或 S_ISGID 位判断

如果文件属性中 S_ISUID 位为 1,则进程启动时会设置 S_ISUID 位,如果文件属性中 S_ISGID 位为 1,则进程启动时会设置 S_ISGID 位。

#include <unistd.h>
int issetugid (void);

函数 issetugid 原型分析:

  • 如果启动进程中 S_ISUID 或 S_ISGID 有一项设置为 1,则返回真,否则返回假。

设置进程实际用户 ID

#include <unistd.h>
int setuid(uid_t  uid);

函数 setuid 原型分析:

  • 此函数成功时返回 0,失败时返回-1 并设置错误码。
  • 参数 uid 是需要设置的进程用户 ID。

如果当前用户为超级用户,即有效用户 ID 为 0,setuid 可以将用户 ID 设置成任何 ID,一旦设置成功,进程的实际用户 ID、有效用户 ID 和保存的设置用户 ID 全部被设置为新 ID。如果当前用户为普通用户,即用户 ID 不为 0,则只修改有效用户 ID,且只能被修改为实际用户 ID 或保存的设置用户 ID。

获取进程实际用户 ID

#include <unistd.h>
uid_t getuid(void);

函数 getuid 原型分析:

  • 返回调用进程实际用户 ID。

设置进程有效用户 ID

#include <unistd.h>
int seteuid(uid_t  euid);

函数 seteuid 原型分析:

  • 此函数成功时返回 0,失败时返回-1 并设置错误码。
  • 参数 euid 是需要设置的进程有效用户 ID。

如果当前用户为超级用户,seteuid 可以将进程有效用户 ID 改成任何 ID。如果当前用户为普通用户,进程有效用户 ID 只能被修改为实际用户 ID 或保存的设置用户 ID。

获取进程有效用户 ID

#include <unistd.h>
uid_t geteuid(void);

函数 geteuid 原型分析:

  • 此函数返回调用进程的有效用户 ID。

设置进程用户组 ID

#include <unistd.h>
int setgid(gid_t  gid);

函数 setgid 原型分析:

  • 此函数成功时返回 0,失败时返回-1 并设置错误号。
  • 参数 gid 是需要设置的进程的用户组 ID。

如果当前用户为超级用户,即用户 ID 为 0,setgid 可以将用户组 ID 改成任何组 ID,但是一旦设置成功,进程的实际用户组 ID、有效用户组 ID 和保存的设置用户组 ID 全部被设置为新组 ID。如果当前用户为普通用户,即用户 ID 不为 0,则只能修改有效用户组 ID,且只能被修改为实际用户组 ID 或保存的设置用户组 ID。

获取进程实际用户组 ID

#include <unistd.h>
gid_t getgid(void);

函数 getgid 原型分析:

  • 此函数返回调用进程的实际用户组 ID。

设置进程有效用户组 ID

#include <unistd.h>
int setegid(gid_t  egid);

函数 setegid 原型分析:

  • 此函数成功返回 0,失败返回-1 并设置错误号。
  • 参数 egid 是需要设置的进程的有效用户组 ID。

如果当前用户为超级用户,setegid 可以将有效用户组 ID 设置成任何组 ID。如果当前用户为普通用户,有效用户组 ID 只能被修改为实际用户组 ID 和保存的设置用户组 ID。

获取进程有效用户组 ID

#include <unistd.h>
gid_t getegid(void);

函数 getegid 原型分析:

  • 此函数返回调用进程有效用户组 ID。

下面实例说明如何设置和获取进程用户 ID 和用户组 ID。

#include <stdio.h>
#include <unistd.h>
int main (int argc, char *argv[])
{
    fprintf(stdout, "uid: %d, gid:%d, euid:%d, egid:%d\n",
            getuid(), getgid(), geteuid(), getegid());   /* 打印用户ID和组ID     */ 
    if (setuid(1) != 0) {                                /* 设置用户ID          */
        fprintf(stderr, "setuid failed\n");
    }
    if (setgid(1) != 0) {                                /* 设置用户组ID         */
        fprintf(stderr, "setgid failed\n");
    }
    fprintf(stdout, "uid: %d, gid:%d, euid:%d, egid:%d\n",
            getuid(), getgid(), geteuid(), getegid());
    return (0);
}

在 SylixOS Shell 中执行程序:

# ./Users_and_UserGroups_ID
uid: 0, gid:0, euid:0, egid:0
setgid failed
uid: 1, gid:0, euid:1, egid:0    

可以看到,进程最初的有效用户 ID 为 0(超级用户),setuid(1)调用将进程的实际用户 ID、有效用户 ID 都设置成了 1,此时进程有效用户 ID 已不是超级用户 ID,当执行 setgid(1)调用时,系统发现目标组 ID 既不是实际用户组 ID,也不是保存的设置用户组 ID(本程序没有设置 S_ISUID 和 S_ISGID,保存的设置用户组 ID 无效),拒绝执行,返回失败。

设置当前进程的扩展用户组 ID

调用此函数进程必须拥有超级用户权限,否则返回失败。

#include <unistd.h>
int setgroups(int groupsun, const gid_t grlist[]);

函数 setgroups 原型分析:

  • 此函数成功返回 0,失败返回-1 并设置错误号。
  • 参数 groupsun 是参数 grlist 数组的大小。
  • 参数 grlist 是扩展用户组 ID 数组。

获取的扩展用户组 ID

#include <unistd.h>
int getgroups(int groupsize, gid_t grlist[]);

函数 setgroups 原型分析:

  • 此函数返回本进程扩展用户组 ID 数量。
  • 参数 groupsize 是参数 grlist[] 数组的大小,如果 groupsize 小于用户扩展用户组 ID 数量,则只填充 groupsize 个用户组 ID。为 0 表示只统计扩展用户组 ID 数量。
  • 参数 grlist 是用于保存扩展用户组 ID 的缓冲区数组。为 0 表示只统计扩展用户组 ID 数量。

进程控制

进程退出

#include <stdlib.h>
void exit(int status);
void _Exit(int status);
#include <unistd.h>
void _exit(int status);

以上函数原型分析:

  • 参数 status 是进程返回码。

上面三个函数都用于进程退出。区别是 exit 函数将调用进程中使用 atexit 函数注册的 hook 函数,而 _Exit 和 _exit 函数不调用,_Exit 和 _exit 函数功能相同。

注册进程退出 hook

#include <stdlib.h>
void atexit(void (*func)(void));

函数 atexit 原型分析:

  • 参数 func 是进程退出 hook 函数,进程正常退出时(main 函数 return 或调用 exit 函数)按 atexit 注册顺序的的逆序调用 hook 函数。

下面实例说明如何使用 atexit 函数和 exit 函数。

#include <stdio.h>
#include <stdlib.h>
void exit_hook1()
{
    fprintf(stdout, "in exit_hook1\n");
}
void exit_hook2()
{
    fprintf(stdout, "in exit_hook2\n");
}
int main (int argc, char *argv[])
{
    atexit(exit_hook1);                                    /* 注册回调函数          */
    atexit(exit_hook2);
    fprintf(stdout, "this is exit hook demo.\n");
    exit(0);
}

在 SylixOS Shell 中执行程序:

# ./Process_Exit
this is exit hook demo.
in exit_hook2
in exit_hook1

等待子进程结束

下面函数等待某个子进程结束。

#include <wait.h>
pid_t wait(int *stat_loc);

函数 wait 原型分析:

  • 此函数成功返回子进程 ID,失败返回-1 并设置错误码。
  • 参数 stat_loc 是子进程退出码。

下面实例展示了如何使用 wait 函数等待子进程。

#include <stdio.h>
#include <unistd.h>
int main (int argc, char *argv[])
{
    int pid;

    sleep(2);                                            /* 等待两秒后退出         */
    pid = getpid();
    fprintf(stdout, "child process %d exit\n", pid);
    exit(1);
}
#include <stdio.h>
#include <spawn.h>
#include <sys/wait.h>

int main (int argc, char *argv[])
{
    pid_t        pid;
    int          status;

    if (posix_spawnp(&pid, "./wait_demo_child", NULL, NULL, NULL, NULL) != 0) { /* 启动进程               */
        fprintf (stderr, "create child process failed\n");
        return  (-1);
    }
    fprintf(stdout, "create child process %d\n", pid);
    pid = wait(&status);                                                        /* 等待子进程退出         */
    fprintf(stdout, "wait returned, child process: %d, status: %d\n", pid, status);
    return (0);
}

在 SylixOS Shell 中执行程序:

# ./Parent_Process_wait
create child process 145
child process 145 exit
wait returned, child process: 145, status: 1

等待子进程状态改变

#include <wait.h>
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);

函数 waitid 原型分析:

  • 如果由于子进程状态改变导致函数返回,返回子进程 ID。如果 options 设置了 WNOHANG  位,且没有符合条件子进程状态发生改变,则不等待且返回 0。其他情况返回-1 并设置错误码。
  • 参数 idtype 指示参数 id 的意义,有以下几种情况。
宏名解释
P_PID等待进程 ID 等于参数 id 的子进程
P_PGID等待进程组 ID 等于参数 id 的子进程
P_ALL等待任一子进程
  • 参数 id 的意义由 idtype 指定,用于指定子进程。
  • 参数 infop 返回接受到的子进程信号,里面记录状态改变的子进程信息。
  • 参数 option 是功能选项,由位掩码组成,如下表所示掩码位:。
宏名解释
WNOHANG如果为 1,函数不等待,当没有子进程状态改变时直接返回
WUNTRACED如果为 2,当子进程进入停止态时返回,否则只有当进程退出时返回

等待指定子进程状态改变

#include <wait.h>
pid_t waitpid(pid_t pid, int *stat_loc, int options);

函数 waitpid 原型分析:

  • 如果由于子进程状态改变导致函数返回,则返回子进程 ID。如果 options 设置了 WNOHANG  位,且没有子进程状态发生改变,则不等待且返回 0。其他情况返回 -1 并设置错误码。
  • 参数 pid 可以有以下几种情况:。
    • pid > 0:表示等待进程号为 pid 的子进程;
    • pid == 0:表示等待与调用进程同组的子进程;
    • pid < -1:表示等待进程组 ID 为 pid 绝对值的子进程。
  • 参数 stat_loc 是子进程退出码。
  • 参数 option 是功能选项,由位掩码组成,如下表所示。
宏名解释
WNOHANG如果为 1,函数不等待,当没有子进程状态改变时直接返回
WUNTRACED如果为 2,当子进程进入停止态时返回,否则只有当进程退出时返回

下面函数展示了如何使用 waitpid 函数,程序中先后创建两个子进程,分别为子进程设置不同的组 ID 并调用 waitpid 函数等待子进程。

#include <stdio.h>
#include <unistd.h>
#include <spawn.h>
#include <sys/wait.h>
int main (int argc, char *argv[])
{
    pid_t        pid;
    int             status;
    if (posix_spawnp(&pid, "./wait_demo_child", NULL, NULL, NULL, NULL) != 0) {  /* 启动进程             */
        fprintf (stderr, "create child process failed\n");
        return  (-1);
    }
    fprintf(stdout, "create child process %d\n", pid);
    setpgid(pid, 10);                                                           /* 设置进程组ID          */
    fprintf(stdout, "waiting for group id 10...\n");
    pid = waitpid(-10, &status, 0);                                             /* 等待子进程退出         */
    fprintf(stdout, "waitpid returned, child process: %d, status: %d\n", pid, status);
    if (posix_spawnp(&pid, "./wait_demo_child", NULL, NULL, NULL, NULL) != 0) { /* 启动进程              */
        fprintf(stderr, "create child process failed\n");
        return  (-1);
    }
    fprintf(stdout, "create child process %d\n", pid);
    setpgid(pid, 11);                                                           /* 设置进程组ID          */
    fprintf(stdout, "waiting for group id 11...\n");
    pid = waitpid(-11, &status, 0);                                             /* 等待子进程退出         */
    fprintf(stdout, "waitpid returned, child process: %d, status: %d\n", pid, status);

    return (0);
}

在 SylixOS Shell 下运行程序。可以看到当子进程组 ID 与父进程调用 waitpid 函数所传 pid 参数绝对值不同时,waitpid 不返回。

# ./waitpid
create child process 147
waiting for group id 10...
child process 147 exit
waitpid returned, child process: 147, status: 1
create child process 148
waiting for group id 11...
child process 148 exit
waitpid returned, child process: 148, status: 1

获取进程资源使用情况

#include <sys/resource.h>
int getrusage(int who, struct rusage *r_usage);

函数 getrusage 原型分析:

  • 此函数成功返回 0,失败返回-1 并设置错误码。
  • 参数 who 是被获取资源的对象,其值如下表所示:。
宏名解释
RUSAGE_SELF获取本进程资源使用情况
RUSAGE_CHILDREN获取子进程资源使用情况
  • 参数 r_usage 返回进程资源使用情况。rusage 结构体定义如下:
struct rusage {
    struct timeval        ru_utime;                    /* 用户态时间                  */
    struct timeval        ru_stime;                    /* 系统态时间                  */
    long                  ru_maxrss;
#define ru_first            ru_ixrss
    long                  ru_ixrss;
    long                  ru_idrss;
    long                  ru_isrss;
    long                  ru_minflt;
    long                  ru_majflt;
    long                  ru_nswap;
    long                  ru_inblock;
    long                  ru_oublock;
    long                  ru_msgsnd;
    long                  ru_msgrcv;
    long                  ru_nsignals;
    long                  ru_nvcsw;
    long                  ru_nivcsw;
#define ru_last            ru_nivcsw
};

注意:
目前,SylixOS 只使用了 ru_utime 和 ru_stime 两个字段,其他字段保留,留待后续扩展。

获取进程时间

#include <sys/times.h>
clock_t times(struct tms *ptms);

函数 times 原型分析:

  • 此函数返回系统当前时间。
  • 参数 ptms 是进程及其子进程时间使用情况,tms 结构定义如下。
struct tms {
    clock_t  tms_utime;                            /*  进程用户态时间               */
    clock_t  tms_stime;                            /*  进程系统态时间               */
    clock_t  tms_cutime;                           /*  子进程用户态时间             */
    clock_t  tms_cstime;                           /*  子进程系统态时间             */
};

需要注意的是,如果参数 ptms 为 NULL,则设置 errno 为 EINVAL 并返回系统时间。

以下伪代码展示了如何获取程序运行时间:

clock_t      start, end, run;
struct tms    tm_start, tm_end;
start = times(&tm_start);
/*
 *  程序代码
 */
...
end = times(&tm_end);
run = end   - start;

进程环境

获取环境变量

#include <stdlib.h>
char *getenv(const char *name);

函数 getenv 原型分析:

  • 此函数成功返回查找到的环境变量字符串指针,失败返回 NULL 并设置错误码。
  • 参数 name 是环境变量名称。

调用 getenv 可以获得当前系统的环境变量,需要注意的是,如果环境变量存在但无关联的值,该函数将返回空字符串,即字符串第一个字符是‘\0’。

设置环境变量

#include <stdlib.h>
int putenv(char *string);

函数 putenv 原型分析:

  • 此函数成功时返回 0,失败时返回非 0 值并设置错误号。
  • 参数 string 是环境变量设置字符串,其格式为:name=value,如果 name 已存在则将删除原来的定义。
#include <stdlib.h>
int setenv(const char *name, const char *value, int overwrite);

函数 setenv 原型分析:

  • 此函数成功返回 0,失败返回-1 并设置错误码。
  • 参数 name 是需要设置的环境变量名称。
  • 参数 value 是环境变量值。
  • 参数 overwrite 表示环境变量已存在时是否覆盖原有环境变量,为 1 表示覆盖,为 0 表示不覆盖。

putenv 函数和 setenv 函数都可以用来设置系统环境变量,不同的是 setenv 可以用更为灵活的方式来设置环境变量。

清除环境变量

#include <stdlib.h>
int unsetenv(const char *name);

函数 unsetenv 原型分析:

  • 此函数成功返回 0,失败返回-1 并设置错误号。
  • 参数 name 是需要清除的环境变量名称。

下面实例展示了环境变量函数的使用方法。

#include <stdio.h>
#include <stdlib.h>
int main (int argc, char *argv[])
{
    const char *env_value = 0;
    const char *env_name  = "ENV_DEMO";
    env_value = getenv(env_name);
    if (env_value) {
        fprintf(stdout, "value of %s before setenv is %s\n", env_name, env_value);
    } else {
        fprintf(stderr, "value of %s is not setted before setenv\n", env_name);
    }
    env_value = "test_value";
    if (setenv(env_name, env_value, 0) != 0) {
        fprintf(stderr, "setenv failed\n");
    } else {
        fprintf(stdout, "set value of %s to %s\n", env_name, env_value);
    }
    env_value = getenv(env_name);
    if (env_value) {
        fprintf(stdout, "value of %s after setevn is %s\n", env_name, env_value);
    } else {
        fprintf(stderr, "value of %s is not setted after setenv\n", env_name);
    }
    if (unsetenv(env_name) != 0) {
        fprintf(stderr, "unsetenv failed\n");
    } else {
        fprintf(stdout, "unset value of %s\n", env_name);
    }
    env_value = getenv(env_name);
    if (env_value) {
        fprintf(stdout, "value of %s after unsetenv is %s\n", env_name, env_value);
    } else {
        fprintf(stderr, "value of %s is not setted after unsetenv\n", env_name);
    }
    return (0);
}

在 SylixOS Shell 中执行程序,结果如下:

# ./Environment_Variable
value of ENV_DEMO is not setted before setenv
set value of ENV_DEMO to test_value
value of ENV_DEMO after setevn is test_value
unset value of ENV_DEMO
value of ENV_DEMO is not setted after unsetenv
文档内容是否对您有所帮助?
有帮助
没帮助