killall 命令的核心功能是:根据进程的名称,找到所有匹配的进程 ID (PID),然后向它们发送一个指定的信号(默认是 SIGTERM,即终止信号)。

(图片来源网络,侵删)
在 C 语言中,我们可以通过调用 Linux/Unix 系统提供的 API 来实现这个功能,主要会用到以下几个头文件和函数:
<dirent.h>: 用于读取目录,特别是/proc目录,这里是存放所有进程信息的地方。<unistd.h>: 提供getpid()等函数。<sys/types.h>: 定义数据类型,如pid_t。<signal.h>: 定义信号相关的常量,如SIGTERM,SIGKILL。<stdlib.h>: 提供atoi()函数,用于将字符串转换为整数。<string.h>: 提供strcmp()函数,用于字符串比较。<stdio.h>: 用于标准输入输出,如printf。
实现思路
- 打开
/proc目录:/proc是一个虚拟文件系统,它包含了正在运行的进程的所有信息,每个进程都有一个以其 PID 命名的子目录。 - 遍历
/proc目录:读取/proc下的每一个条目。 - 识别进程目录:尝试将条目名转换为数字(PID),如果转换成功,则认为它是一个有效的进程目录。
- 读取进程的命令名:进入每个进程目录(
/proc/1234/),读取comm或cmdline文件的内容。/proc/[pid]/comm: 包含进程的基本名称。/proc/[pid]/cmdline: 包含进程启动时执行的完整命令行,第一个\0之前的部分通常是可执行文件的路径或名称,我们通常使用basename来获取这个路径的最后一部分,以便与用户提供的进程名进行比较。
- 比较进程名:将读取到的进程名与用户指定的进程名进行比较。
- 发送信号:如果匹配成功,则使用
kill()函数向该 PID 发送指定的信号。 - 处理错误:在整个过程中,需要妥善处理文件打开、读取失败等错误情况。
C 语言代码实现
下面是一个完整的 C 语言程序,它模仿了 killall 的基本功能。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <errno.h>
// 函数声明
void kill_process_by_name(const char *process_name, int signal);
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "Usage: %s <process_name> [signal]\n", argv[0]);
fprintf(stderr, "Example: %s firefox\n", argv[0]);
fprintf(stderr, "Example: %s nginx -9\n", argv[0]);
return 1;
}
const char *process_name = argv[1];
int signal_to_send = SIGTERM; // 默认信号为 SIGTERM (15)
// 如果提供了第三个参数,则将其作为信号
if (argc > 2) {
signal_to_send = atoi(argv[2]);
if (signal_to_send <= 0) {
fprintf(stderr, "Error: Invalid signal number '%s'. Using SIGTERM.\n", argv[2]);
signal_to_send = SIGTERM;
}
}
printf("Attempting to kill processes with name '%s' using signal %d...\n", process_name, signal_to_send);
kill_process_by_name(process_name, signal_to_send);
return 0;
}
/**
* @brief 根据进程名向所有匹配的进程发送指定信号
* @param process_name 要查找的进程名
* @param signal_to_send 要发送的信号
*/
void kill_process_by_name(const char *process_name, int signal_to_send) {
DIR *dir;
struct dirent *entry;
// 打开 /proc 目录
dir = opendir("/proc");
if (dir == NULL) {
perror("Error opening /proc");
return;
}
// 遍历 /proc 目录下的每一个条目
while ((entry = readdir(dir)) != NULL) {
// 跳过 '.' 和 '..' 以及非数字目录
if (entry->d_type != DT_DIR || strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
// 使用 atoi 尝试转换,如果转换失败则不是 PID 目录
if (atoi(entry->d_name) == 0) {
continue;
}
}
pid_t pid = atoi(entry->d_name);
char comm_path[256];
char cmdline_path[256];
FILE *fp;
char buffer[256];
char *cmd_name = NULL;
// 构建 /proc/[pid]/comm 文件路径
snprintf(comm_path, sizeof(comm_path), "/proc/%d/comm", pid);
// 构建 /proc/[pid]/cmdline 文件路径
snprintf(cmdline_path, sizeof(cmdline_path), "/proc/%d/cmdline", pid);
// 优先尝试从 cmdline 获取进程名,因为它更准确
fp = fopen(cmdline_path, "r");
if (fp) {
if (fgets(buffer, sizeof(buffer), fp) != NULL) {
// cmdline 文件的内容以 '\0' 分隔,我们取第一个部分
// 使用 basename 获取路径的最后一部分
char *slash = strrchr(buffer, '/');
if (slash) {
cmd_name = slash + 1;
} else {
cmd_name = buffer;
}
}
fclose(fp);
}
// cmdline 读取失败或为空,则尝试从 comm 读取
if (cmd_name == NULL || strlen(cmd_name) == 0) {
fp = fopen(comm_path, "r");
if (fp) {
if (fgets(buffer, sizeof(buffer), fp) != NULL) {
cmd_name = buffer;
}
fclose(fp);
}
}
// 如果成功获取了进程名并且与目标名匹配
if (cmd_name != NULL && strcmp(cmd_name, process_name) == 0) {
printf("Found matching process: PID = %d, Name = %s\n", pid, cmd_name);
// 发送信号
if (kill(pid, signal_to_send) == 0) {
printf("Successfully sent signal %d to PID %d.\n", signal_to_send, pid);
} else {
// perror 会打印 "kill: ..." 这样的错误信息
perror("Failed to send signal");
}
}
}
closedir(dir);
}
如何编译和运行
-
保存代码:将上面的代码保存为
my_killall.c。 -
编译:打开终端,使用
gcc进行编译。
(图片来源网络,侵删)gcc my_killall.c -o my_killall
-
运行:
-
启动一个或多个你想测试的进程,
gedit或firefox。 -
然后运行你的程序:
# 终止名为 "gedit" 的所有进程(发送 SIGTERM,信号15) ./my_killall gedit # 强制杀死名为 "nginx" 的所有进程(发送 SIGKILL,信号9) ./my_killall nginx -9
-
代码解释
main函数:处理命令行参数,解析出进程名和要发送的信号,然后调用核心函数kill_process_by_name。kill_process_by_name函数:这是核心逻辑。opendir("/proc"):打开/proc目录。readdir(dir):循环读取目录中的每一个文件/目录名。atoi(entry->d_name):检查目录名是否是有效的数字(PID)。snprintf:安全地构建/proc/[pid]/comm和/proc/[pid]/cmdline的文件路径。fopen和fgets:打开并读取cmdline和comm文件的内容。strrchr(buffer, '/'):在cmdline的路径中查找最后一个 ,以获取可执行文件的名称(basename的简单实现)。strcmp(cmd_name, process_name):比较读取到的进程名和用户指定的进程名。kill(pid, signal_to_send):如果匹配,则调用kill函数发送信号。
- 错误处理:使用
perror函数在kill()失败时打印有意义的错误信息("No such process")。
与真实 killall 的区别
这个 C 语言实现是一个简化版的 killall,它有以下局限性:

(图片来源网络,侵删)
- 不处理僵尸进程:
killall通常不会影响僵尸进程(状态为 'Z' 的进程),因为它们已经不运行了,我们的代码没有检查进程状态。 - 用户权限:我们的代码没有检查当前用户是否有权限杀死目标进程,真实的
killall会因为权限不足而报错。 - 更精确的匹配:真实的
killall可能会有更复杂的匹配规则(-i交互式确认,-r正则表达式匹配等)。 - 信号处理:我们的代码对信号的处理非常基础,真实的
killall对无效信号有更健壮的处理。
尽管如此,这个程序很好地展示了如何使用 C 语言的系统调用来实现一个常见的 Linux 命令的核心功能,是学习 Linux 系统编程的绝佳范例。
