prctl系统调用及示例

prctl 函数详解

1. 函数介绍

prctl 是 Linux 系统中用于进程控制的万能系统调用。可以把 prctl 想象成”进程的控制面板”——它允许你对进程的各种属性进行精细控制,就像调节电视遥控器上的各种设置一样。

prctl 提供了丰富的进程控制功能,包括设置进程名称、控制进程行为、管理安全属性等。它是 Linux 进程管理的重要工具。

相关文章:prctl系统调用及示例-CSDN博客 set_thread_area系统调用及示例 pwritev2系统调用及示例

2. 函数原型

#include <sys/prctl.h>

int prctl(int option, unsigned long arg2, unsigned long arg3,
          unsigned long arg4, unsigned long arg5);

3. 功能

prctl 函数用于控制进程的各种属性和行为。它是一个多功能的系统调用,支持数十种不同的操作选项。

4. 主要参数

  • option: 控制选项(详见下表)
  • arg2, arg3, arg4, arg5: 根据选项而定的参数

5. 常用选项

选项功能参数说明
PR_SET_NAME设置进程名称arg2: 指向名称字符串
PR_GET_NAME获取进程名称arg2: 指向缓冲区
PR_SET_SECCOMP设置安全计算模式seccomp 模式控制
PR_GET_SECCOMP获取安全计算模式
PR_SET_DUMPABLE设置核心转储权限0=不可转储, 1=可转储
PR_GET_DUMPABLE获取核心转储权限
PR_SET_KEEPCAPS设置保持权能标志0=不保持, 1=保持
PR_GET_KEEPCAPS获取保持权能标志
PR_SET_PDEATHSIG设置父进程死亡信号arg2: 信号编号
PR_GET_PDEATHSIG获取父进程死亡信号arg2: 指向信号变量

6. 返回值

  • 成功: 返回 0
  • 失败: 返回 -1,并设置相应的 errno 错误码

7. 常见错误码

  • EINVAL: 参数无效
  • EPERM: 权限不足
  • EFAULT: 参数指针无效
  • EACCES: 访问被拒绝

8. 相似函数或关联函数

  • pthread_setname_np: 设置线程名称
  • getpid: 获取进程 ID
  • kill: 发送信号
  • setuid/setgid: 设置用户/组 ID
  • capset: 设置进程权能

9. 示例代码

示例1:基础用法 – 设置和获取进程名称

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/prctl.h>
#include <string.h>
#include <errno.h>

int main() {
    char old_name[16];
    char new_name[16];
    char current_name[16];
    
    printf("=== prctl 进程名称控制示例 ===\n\n");
    
    // 获取当前进程名称
    if (prctl(PR_GET_NAME, old_name) == 0) {
        old_name[15] = '\0';  // 确保字符串结束
        printf("原始进程名称: %s\n", old_name);
    } else {
        perror("获取进程名称失败");
    }
    
    // 设置新的进程名称
    snprintf(new_name, sizeof(new_name), "MyProcess_%d", getpid() % 1000);
    if (prctl(PR_SET_NAME, new_name) == 0) {
        printf("设置新进程名称: %s\n", new_name);
    } else {
        perror("设置进程名称失败");
    }
    
    // 验证名称是否设置成功
    if (prctl(PR_GET_NAME, current_name) == 0) {
        current_name[15] = '\0';
        printf("当前进程名称: %s\n", current_name);
        printf("设置%s成功\n", 
               strcmp(current_name, new_name) == 0 ? "" : "未完全");
    }
    
    // 恢复原始名称
    if (prctl(PR_SET_NAME, old_name) == 0) {
        printf("恢复原始名称: %s\n", old_name);
    }
    
    printf("\n查看进程名称的方法:\n");
    printf("  ps -ef | grep %d\n", getpid());
    printf("  cat /proc/%d/comm\n", getpid());
    
    return 0;
}

示例2:进程安全控制

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/prctl.h>
#include <signal.h>
#include <errno.h>
#include <string.h>

// 父进程死亡信号处理
void parent_death_handler(int sig) {
    printf("收到父进程死亡信号 %d\n", sig);
    printf("子进程即将退出...\n");
    exit(0);
}

int main() {
    printf("=== prctl 进程安全控制示例 ===\n\n");
    
    // 设置父进程死亡信号
    if (prctl(PR_SET_PDEATHSIG, SIGTERM) == 0) {
        printf("✓ 设置父进程死亡信号为 SIGTERM\n");
    } else {
        printf("✗ 设置父进程死亡信号失败: %s\n", strerror(errno));
    }
    
    // 设置核心转储权限
    int old_dumpable = -1;
    if (prctl(PR_GET_DUMPABLE, &old_dumpable) == 0) {
        printf("当前核心转储权限: %s\n", 
               old_dumpable ? "允许" : "禁止");
    }
    
    // 禁止核心转储
    if (prctl(PR_SET_DUMPABLE, 0) == 0) {
        printf("✓ 禁止核心转储\n");
        
        // 验证设置
        int new_dumpable = -1;
        if (prctl(PR_GET_DUMPABLE, &new_dumpable) == 0) {
            printf("验证: 核心转储权限现在是 %s\n", 
                   new_dumpable ? "允许" : "禁止");
        }
    }
    
    // 恢复核心转储权限
    if (old_dumpable != -1) {
        prctl(PR_SET_DUMPABLE, old_dumpable);
    }
    
    printf("\n安全控制说明:\n");
    printf("1. PR_SET_PDEATHSIG: 父进程死亡时发送信号\n");
    printf("2. PR_SET_DUMPABLE: 控制核心转储权限\n");
    printf("3. 提高进程安全性,防止敏感信息泄露\n");
    
    return 0;
}

示例3:完整的进程控制工具

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/prctl.h>
#include <string.h>
#include <errno.h>
#include <getopt.h>

// 显示帮助信息
void show_help(const char *program_name) {
    printf("用法: %s [选项]\n", program_name);
    printf("\n选项:\n");
    printf("  -n, --name=NAME        设置进程名称\n");
    printf("  -s, --show             显示当前进程信息\n");
    printf("  -d, --dumpable=0|1     设置核心转储权限\n");
    printf("  -p, --pdeathsig=SIG    设置父进程死亡信号\n");
    printf("  -h, --help             显示此帮助信息\n");
    printf("\n示例:\n");
    printf("  %s -n MyNewName        # 设置进程名称\n", program_name);
    printf("  %s -s                  # 显示进程信息\n", program_name);
    printf("  %s -d 0                # 禁止核心转储\n", program_name);
    printf("  %s -p 15               # 设置父进程死亡信号为 SIGTERM\n", program_name);
}

// 显示当前进程信息
void show_process_info() {
    char name[16];
    
    printf("=== 当前进程信息 ===\n");
    printf("进程 ID: %d\n", getpid());
    printf("父进程 ID: %d\n", getppid());
    printf("用户 ID: %d\n", getuid());
    printf("组 ID: %d\n", getgid());
    
    // 获取进程名称
    if (prctl(PR_GET_NAME, name) == 0) {
        name[15] = '\0';
        printf("进程名称: %s\n", name);
    }
    
    // 获取核心转储权限
    int dumpable = -1;
    if (prctl(PR_GET_DUMPABLE, &dumpable) == 0) {
        printf("核心转储: %s\n", dumpable ? "允许" : "禁止");
    }
    
    // 获取父进程死亡信号
    int pdeathsig = -1;
    if (prctl(PR_GET_PDEATHSIG, &pdeathsig) == 0) {
        if (pdeathsig > 0) {
            printf("父进程死亡信号: %d\n", pdeathsig);
        } else {
            printf("父进程死亡信号: 无\n");
        }
    }
}

int main(int argc, char *argv[]) {
    char *new_name = NULL;
    int dumpable = -1;
    int pdeathsig = -1;
    int show_info = 0;
    
    printf("=== prctl 进程控制工具 ===\n\n");
    
    // 解析命令行参数
    static struct option long_options[] = {
        {"name",      required_argument, 0, 'n'},
        {"show",      no_argument,       0, 's'},
        {"dumpable",  required_argument, 0, 'd'},
        {"pdeathsig", required_argument, 0, 'p'},
        {"help",      no_argument,       0, 'h'},
        {0, 0, 0, 0}
    };
    
    int opt;
    while ((opt = getopt_long(argc, argv, "n:sd:p:h", long_options, NULL)) != -1) {
        switch (opt) {
            case 'n':
                new_name = optarg;
                break;
            case 's':
                show_info = 1;
                break;
            case 'd':
                dumpable = atoi(optarg);
                break;
            case 'p':
                pdeathsig = atoi(optarg);
                break;
            case 'h':
                show_help(argv[0]);
                return 0;
            default:
                fprintf(stderr, "使用 '%s --help' 查看帮助信息\n", argv[0]);
                return 1;
        }
    }
    
    // 显示信息
    if (show_info || (!new_name && dumpable == -1 && pdeathsig == -1)) {
        show_process_info();
        printf("\n");
    }
    
    // 设置进程名称
    if (new_name) {
        if (prctl(PR_SET_NAME, new_name) == 0) {
            printf("✓ 设置进程名称为: %s\n", new_name);
        } else {
            printf("✗ 设置进程名称失败: %s\n", strerror(errno));
        }
    }
    
    // 设置核心转储权限
    if (dumpable != -1) {
        if (prctl(PR_SET_DUMPABLE, dumpable) == 0) {
            printf("✓ %s核心转储\n", dumpable ? "允许" : "禁止");
        } else {
            printf("✗ 设置核心转储权限失败: %s\n", strerror(errno));
        }
    }
    
    // 设置父进程死亡信号
    if (pdeathsig > 0) {
        if (prctl(PR_SET_PDEATHSIG, pdeathsig) == 0) {
            printf("✓ 设置父进程死亡信号为: %d\n", pdeathsig);
        } else {
            printf("✗ 设置父进程死亡信号失败: %s\n", strerror(errno));
        }
    }
    
    // 显示使用建议
    if (new_name || dumpable != -1 || pdeathsig != -1) {
        printf("\n=== 使用建议 ===\n");
        printf("进程名称控制:\n");
        printf("  - 用于进程标识和监控\n");
        printf("  - 方便系统管理工具识别\n");
        printf("  - 长度限制为 15 个字符\n");
        printf("\n安全控制:\n");
        printf("  - 禁止核心转储保护敏感信息\n");
        printf("  - 设置父进程死亡信号实现优雅退出\n");
        printf("  - 提高进程的安全性和可控性\n");
    }
    
    return 0;
}

编译和运行说明

# 编译示例程序
gcc -o prctl_example1 example1.c
gcc -o prctl_example2 example2.c
gcc -o prctl_example3 example3.c

# 运行示例
./prctl_example1
./prctl_example2
./prctl_example3 --help
./prctl_example3 -s
./prctl_example3 -n TestProcess
./prctl_example3 -d 0

系统要求检查

# 检查内核版本(需要 2.1.57+)
uname -r

# 检查 prctl 支持
grep -w prctl /usr/include/asm/unistd_64.h

# 查看进程信息
cat /proc/self/comm
ps -p $$ -o pid,ppid,comm,cmd

重要注意事项

  1. 权限要求: 大多数操作不需要特殊权限
  2. 名称长度: 进程名称最长 15 个字符(加上终止符共 16 字节)
  3. 错误处理: 始终检查返回值和 errno
  4. 平台相关: prctl 是 Linux 特有的系统调用
  5. 安全性: 合理使用安全控制选项

实际应用场景

  1. 进程管理: 设置有意义的进程名称
  2. 安全控制: 控制核心转储和敏感信息
  3. 容器技术: 容器化进程控制
  4. 服务管理: 系统服务进程管理
  5. 调试工具: 调试和监控工具使用

常用选项详解

// 进程名称控制
prctl(PR_SET_NAME, "MyProcess");    // 设置进程名称
prctl(PR_GET_NAME, name_buffer);    // 获取进程名称

// 安全控制
prctl(PR_SET_DUMPABLE, 0);          // 禁止核心转储
prctl(PR_GET_DUMPABLE, &dumpable);  // 获取核心转储权限
prctl(PR_SET_PDEATHSIG, SIGTERM);    // 设置父进程死亡信号

// 权能控制
prctl(PR_SET_KEEPCAPS, 1);          // 设置保持权能标志
prctl(PR_GET_KEEPCAPS, &keepcaps);  // 获取保持权能标志

// 内存保护
prctl(PR_SET_MM, PR_SET_MM_START_BRK, addr);  // 设置堆起始地址

最佳实践

// 安全的进程名称设置
int safe_set_process_name(const char *name) {
    if (!name) {
        errno = EINVAL;
        return -1;
    }
    
    // 验证名称长度
    if (strlen(name) >= 16) {
        fprintf(stderr, "进程名称过长,最多 15 个字符\n");
        errno = EINVAL;
        return -1;
    }
    
    // 设置进程名称
    int result = prctl(PR_SET_NAME, name);
    if (result == -1) {
        fprintf(stderr, "设置进程名称失败: %s\n", strerror(errno));
    }
    
    return result;
}

// 获取进程信息的安全函数
int get_process_info_safe(char *name, int *dumpable, int *pdeathsig) {
    int result = 0;
    
    // 获取进程名称
    if (name) {
        if (prctl(PR_GET_NAME, name) == -1) {
            result = -1;
        } else {
            name[15] = '\0';  // 确保字符串结束
        }
    }
    
    // 获取核心转储权限
    if (dumpable) {
        if (prctl(PR_GET_DUMPABLE, dumpable) == -1) {
            result = -1;
        }
    }
    
    // 获取父进程死亡信号
    if (pdeathsig) {
        if (prctl(PR_GET_PDEATHSIG, pdeathsig) == -1) {
            result = -1;
        }
    }
    
    return result;
}

这些示例展示了 prctl 函数的各种使用方法,从基础的进程名称设置到完整的进程控制工具,帮助你全面掌握 Linux 系统中的进程控制机制。

此条目发表在linux文章分类目录。将固定链接加入收藏夹。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注