OpenSUSE 42.3下如何修改C代码以sudo方式终止子进程?
问题分析与解决方案
版本差异原因
OpenSUSE 13.2与42.3的核心差异在于sudo版本的行为变化:
- OpenSUSE 13.2使用的早期sudo版本中,会直接
exec目标命令(如sleep),不会额外fork子进程,因此vfork返回的PID就是sleep进程的真实PID,此时kill该PID可以直接终止目标进程。 - OpenSUSE 42.3使用的新版sudo引入了进程隔离机制,sudo会先fork一个子进程来执行目标命令,
vfork返回的PID是sudo父进程的PID。当sudo完成权限切换并启动sleep后,sudo父进程会立即退出,此时你尝试kill的PID已经是已退出的进程,而sleep进程作为sudo子进程继续运行(父进程变为init/systemd),自然无法被终止。
为什么pkill -P <pid>不稳定
pkill -P是通过父进程PID查找子进程。在sudo父进程尚未退出的短暂窗口内,sleep的父进程还是sudo的PID,此时pkill能找到目标进程;但如果代码执行到system(pkill)时,sudo父进程已经退出,sleep的父进程变为init,pkill就无法匹配到目标,因此表现不稳定。
可行的代码改写方案
要稳定终止目标进程,需要先获取sleep的真实PID,再执行kill操作。以下是改写后的代码:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> int main (int argc, char *argv[]) { char execName[80] = "/usr/bin/sudo"; char cmd[256]; int pid = 0; int SIG = 15; if (argc > 1) SIG = atoi(argv[1]); printf("Sudo sleeping for 30 seconds.\n"); printf("Sudo killing with signal %d.\n", SIG); printf("Use `ps aux | grep sleep` within 30 seconds to verify it was killed. Only this grep should show.\n"); if ((pid = vfork()) == 0) { execl (execName, execName, "/bin/sleep", "30", (char *)NULL); perror(execName); exit (-1); } else { if (pid == -1) perror("Bad PID for sleeper"); fprintf( stderr, "sudo process PID = %d\n", pid ); } // 短暂延迟,确保sudo已启动sleep进程 usleep(100000); // 100ms,可根据实际情况调整 // 获取sleep进程的真实PID(基于sudo进程的子进程) char get_pid_cmd[256]; char sleep_pid[32] = {0}; sprintf(get_pid_cmd, "/usr/bin/sudo ps -o pid= --ppid %d", pid); FILE *fp = popen(get_pid_cmd, "r"); if (fp != NULL) { if (fgets(sleep_pid, sizeof(sleep_pid), fp) != NULL) { // 去除字符串中的换行符和空格 sleep_pid[strcspn(sleep_pid, "\n\t ")] = '\0'; if (strlen(sleep_pid) > 0) { sprintf(cmd, "/usr/bin/sudo kill -%d %s", SIG, sleep_pid); printf("Killing sleep process (PID: %s)...\n", sleep_pid); system(cmd); } else { fprintf(stderr, "Failed to get sleep process PID\n"); } } pclose(fp); } else { perror("Failed to run ps command"); } return 0; }
关键改进点:
- 添加短暂延迟:确保sudo有足够时间启动
sleep进程,避免在sudo父进程尚未fork出sleep时执行ps查询。 - 通过
ps --ppid获取真实PID:精准匹配sudo父进程的子进程(即sleep),避免误杀其他进程。 - 处理PID字符串:去除多余的空白字符,确保
kill命令能正确识别PID。
额外优化建议
如果sudoers配置允许,也可以考虑直接让当前进程以sudo权限运行,这样无需通过sudo启动子进程,直接fork+exec目标命令后,用kill()系统调用即可终止进程,避免PID匹配的问题。但这需要调整遗留程序的运行权限逻辑,需根据实际情况评估。
内容的提问来源于stack exchange,提问作者Hyreon
相关产品推荐
相关产品推荐

