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 函数的使用方法。
程序清单 exec实例子进程(child_process)
#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);
}
程序清单 execl实例(execl)
#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
程序清单 execle实例(execle)
#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
程序清单 execve实例(execve)
#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_FIFO | SCHED_FIFO 先到先服务实时调度策略 |
LW_OPTION_SCHED_RR | SCHED_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);
}
本实例使用程序清单 exec实例子进程(child_process)程序作为子进程可执行文件,在 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 进程支持自定义配置功能,配置文件为 "/etc/soload.conf" 对于需要配置进程启动时的行为或者指定可执行文件和动态库路径,可以向该文件中增加配置项,该文件的语法规则如下:
<进程文件绝对路径> <行为1>:<行为2>
如下配置可以开启浮点异常和 DSP 异常功能:
/apps/fpu/app_fpu [FPU_EXC_EN]:[DSP_EXC_EN]
可以指定执行文件或者动态库的存放路径,该功能类似环境变量LD_LIBRARY_PATH或PATH的功能,语法如下:
/usr/bin:/bin:/usr/pkg/sbin:/sbin:/usr/local/bin
当前配置文件中指定为文件路径时与LD_LIBRARY_PATH或PATH的查找优先级如下:
查找配置文件 > LD_LIBRARY_PATH > PATH
因此如果用户想改变环境变量的默认配置,修改该配置文件时一个不错的办法。
进程调度
进程是系统资源分配的基本单位(可以看成是资源的容器),线程是调度的基本单位,因此 SylixOS 的进程调度是指对进程的主线程进行调度。
设置 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, ¶m) != 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, ¶m) != 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