Skip to content

Latest commit

 

History

History
192 lines (141 loc) · 5.15 KB

069_pid.md

File metadata and controls

192 lines (141 loc) · 5.15 KB

069 任务 ID

1. 原理说明

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).

2. 代码分析

2.1 系统调用链

根据系统调用流程,注册系统调用链:

//--> 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

2.2 进程标识符

为了实现获取进程 ID 和父进程 ID 的功能,需要在任务控制块 TCB 中加入两个新成员:

  • pid:当前进程 ID
  • ppid:父进程 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() 一节。

2.3 sys_getpid & sys_getppid

现在就可以实现 getpid()getppid() 的内核处理功能了,只需获取对应任务控制块 TCB 中记录的 pidppid 字段,并进行返回即可。

//--> kernel/task.c

pid_t sys_getpid() {
    return current_task()->pid;
}

pid_t sys_getppid() {
    return current_task()->ppid;
}

3. 功能测试

我们使用用户线程 user_init_thread 和内核线程 test_thread 进行测试,使得这两个线程每经过一定的时间间隔(user_init_thread 间隔为 1s, test_thread 间隔为 2s),就打印出任务的 pidppid 信息。

//--> 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
...

4. FAQ

1. 触发了内核栈溢出?

可以参考 050 任务阻塞和就绪 中的 FAQ 来解决。