进程控制是 Linux 系统编程的核心内容,涉及进程身份标识获取、工作环境修改、权限管理以及资源限制等方面。在嵌入式 Linux 应用开发中,合理使用这些接口对系统安全和稳定至关重要。
为什么需要进程控制?
进程身份标识体系
Linux 中每个进程都关联四个 ID:
| ID 类型 | 含义 | 获取函数 | 说明 |
|---|---|---|---|
| PID | 进程ID | getpid() |
每个进程唯一 |
| PPID | 父进程ID | getppid() |
创建当前进程的进程 |
| UID | 用户ID | getuid() |
实际用户(登录用户) |
| GID | 组ID | getgid() |
实际用户组 |
与之对应的还有 有效 ID(EUID/EGID),用于权限检查。例如 setuid() 程序运行时 EUID 可能是 root,而实际 UID 是普通用户。
umask 与文件创建
每个进程都有一个 umask 值,它是一个权限掩码,用于在创建文件时屏蔽不需要的权限位。例如 umask 为 0022 时,创建的文件不会具有 group-write 和 other-write 权限。
进程资源限制
Linux 通过 rlimit 结构体为每个进程设定资源上限,包括:
#include <unistd.h>
#include <sys/types.h>
pid_t getpid(void); // 返回当前进程PID
pid_t getppid(void); // 返回父进程PID
uid_t getuid(void); // 返回实际用户ID(real user id)
uid_t geteuid(void); // 返回有效用户ID(effective user id)
gid_t getgid(void); // 返回实际组ID
gid_t getegid(void); // 返回有效组ID
返回值说明:
getpid() / getppid():返回 pid_t 类型(通常为 int),不会失败getuid() / geteuid() / getgid() / getegid():返回对应的 ID 值,不会失败#include <unistd.h>
int chdir(const char *path); // 改变当前工作目录
int fchdir(int fd); // 通过文件描述符改变工作目录
#include <unistd.h>
int chroot(const char *path); // 更改根目录(仅 root 权限)
chdir():成功返回 0,失败返回 -1chroot():需要 CAP_SYS_CHROOT 权限(通常为 root),将进程的根目录 / 重定向到指定路径#include <sys/stat.h>
mode_t umask(mode_t mask); // 设置文件创建权限掩码
mask:新的 umask 值(八进制表示,如 0022)open()/creat()/mkdir() 等创建操作,不影响 chmod() 的显式设置#include <unistd.h>
int setuid(uid_t uid); // 设置有效用户ID
int seteuid(uid_t euid); // 设置有效用户ID(推荐)
int setgid(gid_t gid); // 设置有效组ID
int setegid(gid_t egid); // 设置有效组ID(推荐)
| 函数 | 作用 | 特权要求 | 说明 |
|---|---|---|---|
setuid() |
设置 real+saved+effective UID | root | 非 root 调用只能设置一次 |
seteuid() |
仅设置 effective UID | root | 可反复切换,更灵活 |
setgid() |
设置 real+saved+effective GID | root | 同 setuid |
setegid() |
仅设置 effective GID | root | 可反复切换 |
嵌入式推荐做法:使用 seteuid()/setegid() 在 root 和普通用户之间切换,而非 setuid()。
#include <sys/resource.h>
int getrlimit(int resource, struct rlimit *rlim);
int setrlimit(int resource, const struct rlimit *rlim);
struct rlimit {
rlim_t rlim_cur; // 软限制(当前生效值)
rlim_t rlim_max; // 硬限制(软限制的上限)
};
常用 resource 常量:
| 常量 | 含义 | 典型值 |
|---|---|---|
| RLIMIT_STACK | 栈大小 | 默认 8MB |
| RLIMIT_NOFILE | 最大打开文件数 | 默认 1024 |
| RLIMIT_NPROC | 最大子进程数 | 默认系统决定 |
| RLIMIT_CPU | CPU 时间(秒) | 无限 |
| RLIMIT_AS | 最大虚拟内存(字节) | 无限 |
| RLIMIT_DATA | 数据段大小(字节) | 无限 |
| RLIMIT_FSIZE | 最大文件大小(字节) | 无限 |
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
int main(void)
{
pid_t pid, ppid;
uid_t uid, euid;
gid_t gid, egid;
struct passwd *pw;
struct group *gr;
pid = getpid();
ppid = getppid();
uid = getuid();
euid = geteuid();
gid = getgid();
egid = getegid();
printf("PID: %d\n", pid);
printf("PPID: %d\n", ppid);
pw = getpwuid(uid);
if (pw) {
printf("User: %s (UID=%d)\n", pw->pw_name, uid);
}
if (uid != euid) {
pw = getpwuid(euid);
if (pw) {
printf("EUser: %s (EUID=%d)\n", pw->pw_name, euid);
}
}
gr = getgrgid(gid);
if (gr) {
printf("Group: %s (GID=%d)\n", gr->gr_name, gid);
}
if (gid != egid) {
gr = getgrgid(egid);
if (gr) {
printf("EGroup: %s (EGID=%d)\n", gr->gr_name, egid);
}
}
return 0;
}
编译命令:
gcc -o proc_info proc_info.c
./proc_info
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
int main(void)
{
char buf[1024];
mode_t old_mask;
/* 1. 获取并打印当前工作目录 */
if (getcwd(buf, sizeof(buf)) == NULL) {
perror("getcwd");
return 1;
}
printf("Current dir: %s\n", buf);
/* 2. 修改工作目录 */
if (chdir("/tmp") < 0) {
perror("chdir");
return 1;
}
if (getcwd(buf, sizeof(buf)) == NULL) {
perror("getcwd");
return 1;
}
printf("After chdir: %s\n", buf);
/* 3. umask 示例 */
old_mask = umask(0000); /* 清除所有掩码 */
printf("Old umask: %04o\n", old_mask);
/* 创建文件,权限为 0666 & ~0000 = 0666 */
int fd = open("test_perm.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);
if (fd >= 0) {
close(fd);
printf("Created test_perm.txt with umask 0000\n");
}
umask(0022); /* 恢复默认掩码 */
fd = open("test_default.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);
if (fd >= 0) {
close(fd);
printf("Created test_default.txt with umask 0022\n");
}
/* 清理 */
unlink("test_perm.txt");
unlink("test_default.txt");
return 0;
}
编译命令:
gcc -o dir_perm dir_perm.c
./dir_perm
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <string.h>
int main(void)
{
uid_t real_uid, effective_uid;
real_uid = getuid();
effective_uid = geteuid();
printf("Before seteuid:\n");
printf(" UID=%d, EUID=%d\n", real_uid, effective_uid);
if (effective_uid == 0) {
/* 当前是 root,降到普通用户 */
if (seteuid(real_uid) < 0) {
perror("seteuid");
return 1;
}
printf("After seteuid (drop privileges):\n");
printf(" UID=%d, EUID=%d\n", getuid(), geteuid());
/* 尝试以普通用户身份访问 /etc/shadow */
FILE *fp = fopen("/etc/shadow", "r");
if (fp == NULL) {
printf(" Cannot open /etc/shadow (expected, no root)\n");
} else {
printf(" WARNING: opened /etc/shadow!\n");
fclose(fp);
}
/* 恢复 root 权限 */
if (seteuid(0) < 0) {
perror("seteuid restore");
return 1;
}
printf("After restore root:\n");
printf(" UID=%d, EUID=%d\n", getuid(), geteuid());
} else {
printf("Not running as root, skip privilege demo\n");
}
return 0;
}
编译命令:
gcc -o priv_demo priv_demo.c
sudo ./priv_demo # 需要 sudo 起始权限
#include <stdio.h>
#include <stdlib.h>
#include <sys/resource.h>
#include <errno.h>
#include <string.h>
static void print_rlimit(const char *name, int resource)
{
struct rlimit rl;
if (getrlimit(resource, &rl) < 0) {
perror("getrlimit");
return;
}
printf("%-20s soft=%-15ld hard=%-15ld\n",
name,
(long)rl.rlim_cur,
(long)rl.rlim_max);
}
int main(void)
{
struct rlimit rl;
printf("=== Current Resource Limits ===\n\n");
print_rlimit("RLIMIT_STACK", RLIMIT_STACK);
print_rlimit("RLIMIT_NOFILE", RLIMIT_NOFILE);
print_rlimit("RLIMIT_NPROC", RLIMIT_NPROC);
print_rlimit("RLIMIT_CPU", RLIMIT_CPU);
print_rlimit("RLIMIT_AS", RLIMIT_AS);
print_rlimit("RLIMIT_DATA", RLIMIT_DATA);
print_rlimit("RLIMIT_FSIZE", RLIMIT_FSIZE);
/* 修改栈大小为 4MB */
printf("\n--- Set stack to 4MB ---\n");
if (getrlimit(RLIMIT_STACK, &rl) < 0) {
perror("getrlimit");
return 1;
}
rl.rlim_cur = 4 * 1024 * 1024;
if (setrlimit(RLIMIT_STACK, &rl) < 0) {
perror("setrlimit");
return 1;
}
print_rlimit("RLIMIT_STACK", RLIMIT_STACK);
/* 修改最大打开文件数为 512 */
printf("\n--- Set nofile to 512 ---\n");
if (getrlimit(RLIMIT_NOFILE, &rl) < 0) {
perror("getrlimit");
return 1;
}
rl.rlim_cur = 512;
if (setrlimit(RLIMIT_NOFILE, &rl) < 0) {
perror("setrlimit");
return 1;
}
print_rlimit("RLIMIT_NOFILE", RLIMIT_NOFILE);
return 0;
}
编译命令:
gcc -o rlimit_demo rlimit_demo.c
./rlimit_demo
getpid() 不会失败:返回值始终有效,无需检查错误setuid() vs seteuid() 区别:
setuid() 同时修改 real、effective、saved UID,非 root 调用只能执行一次seteuid() 只修改 effective UID,可反复切换,嵌入式中推荐使用chroot() 需要 root 权限:且被 chroot 的进程仍可通过 chdir("..") 逃逸(需额外防护)open()/creat()/mkdir() 等创建类系统调用,chmod() 不受影响setrlimit() 只能降低不能提高:非 root 进程只能将软限制降低到硬限制以下RLIMIT_NPROC 限制的是 real UID 的进程数:有效 UID 的进程不受此限制getcwd() 返回的是绝对路径:如果当前目录已被删除,行为依赖实现RLIMIT_NOFILE 和 RLIMIT_STACK,避免系统耗尽Q1:getuid() 和 geteuid() 有什么区别?什么时候会不同?
A:getuid() 返回实际用户ID(登录用户),geteuid() 返回有效用户ID(用于权限检查)。当进程通过 setuid 程序(如 passwd、sudo)运行时,EUID 会被设置为文件所有者(通常是 root),此时两者不同。正常 fork 出的子进程两者相同。
Q2:setuid() 和 seteuid() 有什么区别?嵌入式开发中如何选择?
A:setuid() 修改 real、effective、saved UID 三个值,非 root 调用只能执行一次(不可逆)。seteuid() 只修改 effective UID,可反复切换。嵌入式中推荐 seteuid():程序以 root 启动后可降到普通用户执行不敏感操作,再在需要时恢复 root 权限。
Q3:umask 的作用是什么?它影响 chmod() 吗?
A:umask 是文件创建权限掩码,在 open(O_CREAT) 时自动屏蔽对应权限位。例如 umask=022 创建的文件权限为 0644 而非 0666。umask 不影响 chmod(),因为 chmod() 是显式设置权限。
Q4:setrlimit() 能将软限制提高到硬限制以上吗?
A:不能。非 root 进程只能将软限制设置为 ≤ 硬限制的值。要提高软限制,必须先提高硬限制(需要 root 权限),然后再降低软限制。root 进程可以将硬限制设置为任意值(包括 RLIM_INFINITY)。
Q5:嵌入式系统中为什么需要限制进程的文件描述符数量?
A:嵌入式设备内存和内核资源有限,如果不设限制,bug 程序可能打开大量文件描述符导致系统无法分配新的 fd("Too many open files"),影响其他关键进程。通过 setrlimit(RLIMIT_NOFILE, ...) 可以为每个进程设定合理上限,实现资源隔离。