pid_t getpid();
pid_t getppid();
节选自 man 2 getpid
:
DESCRIPTION
getpid() returns the process ID (PID) of the calling process. (This is often used by routines that generate
unique temporary filenames.)
getppid() returns the process ID of the parent of the calling process. This will be either the ID of the process
that created this process using fork(), or, if that process has already terminated, the ID of the process to
which this process has been reparented (either init(1) or a "subreaper" process defined via the prctl(2)
PR_SET_CHILD_SUBREAPER operation).
根据系统调用流程,注册系统调用链:
//--> include/xos/syscall.h
// 系统调用号
typedef enum syscall_t {
...
SYS_GETPID = 20, // new
SYS_GETPPID = 64, // new
} syscall_t;
/***** 声明用户态封装后的系统调用原型 *****/
pid_t getpid(); // new
pid_t getppid(); // new
//--> lib/syscall.c
// new
pid_t getpid() {
return _syscall0(SYS_GETPID);
}
// new
pid_t getppid() {
return _syscall0(SYS_GETPPID);
}
//--> kernel/syscall.c
// 初始化系统调用
void syscall_init() {
...
syscall_table[SYS_GETPID] = sys_getpid; // new
syscall_table[SYS_GETPPID] = sys_getppid; // new
}
因为 getpid()
和 getppid()
这两个系统调用返回的都是进程标识符(本质上是整数),所以我们还需要定义一个新类型来表示。
//--> include/xos/types.h
// 进程标识符
typedef i32 pid_t; // new
为了实现获取进程 ID 和父进程 ID 的功能,需要在任务控制块 TCB 中加入两个新成员:
pid
:当前进程 IDppid
:父进程 ID
//--> include/xos/task.h
// 任务控制块 TCB
typedef struct task_t {
...
pid_t pid; // 进程 id
pid_t ppid; // 父进程 id
...
} task_t;
然后我们需要在创建任务时分配任务 ID。为了简单起见,我们将新创建的任务,位于任务队列 task_queue
的下标作为任务 ID。这需要我们对 get_free_task()
和 task_create()
这两个函数进行一些修改。
//--> kernel/task.c
// 从任务队列获取一个空闲的任务,并分配 TCB
static task_t *get_free_task() {
for (size_t i = 0; i < NUM_TASKS; i++) {
if (task_queue[i] == NULL) {
// new
task_t *task = (task_t *)kalloc_page(1);
memset(task, 0, PAGE_SIZE); // 清空 TCB 所在的页
task->pid = i; // 分配进程 id
task_queue[i] = task;
return task_queue[i];
}
}
return NULL;
}
static task_t *task_create(target_t target, const char *name, u32 priority, u32 uid) {
task_t *task = get_free_task();
// remove - memset(task, 0, PAGE_SIZE); // 清空 TCB 所在的页
...
}
我们在寻找空闲任务 get_free_task()
时就对任务进行 ID 分配,这需要将任务控制块 TCB 所在页面的清空处理,提前至 get_free_task()
当中。
细心的读者已经发现,这里我们只是实现了当前进程 ID 的分配,并没有实现父进程 ID 的分配。这是因为父进程和子进程的关系,或者说进程树的概念,只有进程在进行
fork()
时才会出现,所以我们将父进程 ID 的处理推迟至实现系统调用fork()
一节。
现在就可以实现 getpid()
和 getppid()
的内核处理功能了,只需获取对应任务控制块 TCB 中记录的 pid
和 ppid
字段,并进行返回即可。
//--> kernel/task.c
pid_t sys_getpid() {
return current_task()->pid;
}
pid_t sys_getppid() {
return current_task()->ppid;
}
我们使用用户线程 user_init_thread
和内核线程 test_thread
进行测试,使得这两个线程每经过一定的时间间隔(user_init_thread
间隔为 1s, test_thread
间隔为 2s),就打印出任务的 pid
和 ppid
信息。
//--> kernel/thread.c
// 初始化任务 init 的用户态线程
static void user_init_thread() {
size_t counter = 0;
while (true) {
printf("init thread pid: %d, ppid: %d, counter: %d\n", getpid(), getppid(), counter++);
sleep(1000);
}
}
// 测试任务 test
void test_thread() {
irq_enable();
u32 counter = 0;
while (true) {
printf("test thread pid: %d, ppid: %d, counter: %d\n", getpid(), getppid(), counter++);
sleep(2000);
}
}
从这里可以看出,使用用户线程的优势在于在系统调用结束后,返回执行流时会自动打开中断,而内核线程需要手动开启。具体原理可以参考 <063 进入用户模式>
预期输出如下格式:
init thread pid: 1, ppid: 0, counter: 0
test thread pid: 2, ppid: 0, counter: 0
...
1. 触发了内核栈溢出?
可以参考 050 任务阻塞和就绪 中的 FAQ 来解决。