目录

从0开始的操作系统手搓教程28实现Syscall架构体系

从0开始的操作系统手搓教程28:实现Syscall架构体系


上一节中,我们已经干了一个大事情,那就是实现了用户级的线程(可以叫线程了,只需要派发一个打算以特权级为3的执行函数,我们就可以执行它)

可是我们终归是要使用操作系统的一些服务的,这可咋办,直接让自己来干,好像不对,特权级不允许我们这样做,但是我们的确需要知道一些事情。这个问题的解决思路是——按照我们内核规定的模式,请求我们操作系统服务,而不是越过操作系统自己把这个事情办了。上述的“规定的模式”,就是我们所谈到的系统调用。

所以,syscall

syscall就是这样我们上面谈到的东西,想要用系统的服务,可以,必须透过我们的操作系统规定的syscall接口请求,我们的操作系统会做一部分检查,检查用户的请求是不是合理的,不是合理的,我们就会返回状态告知用户程序,合理的话,我们就会完成用户的合理请求。

系统调用一般是使用中断实现的——很好,你知道要做什么了,我们要修改一下中断的interrupt.S来安装我们系统调用的Hook了,等等,难道我们都是要给每一个系统调用整一个中断嘛?啊,没那个必要,我们直接将系统调用号带进syscall的中断回调就好了,按照调用号进行偏移寻找我们的对应的处理服务函数。事情看起来就像是这样的

https://i-blog.csdnimg.cn/direct/8c3778751279488294f367254411dbf1.png

这一个流程看下来,我们这下知道了我们要做的事情,就是依次的做

  • 注册我们的0x80号中断给系统调用(0x80无所谓,这里是随了Linux的0x80系统调用了,至于你换其他的随意,只要能正确对应,中断不会跳飞,那这个就是没有问题的)
  • 准备好相应的用户接口和与之对应的系统服务接口
  • 实现桥接的syscall宏,负责处理参数传递和实际上发起调用的流程。

我们接下来,就要准备整个大活了

注册0x80的Hook

第一件事情,把我们支持的中断向量个数改一下:

#define IDT_DESC_CNT (0x81) // The number of interrupt descriptors in the IDT

现在我们准备支持0x80号中断,自然我们支持的中断个数就要增加到0x80了。

下一步,就是添加我们的Hook:

extern uint32_t syscall_handler(void); // External syscall handler function
...
// Function to initialize the IDT descriptor table
static void idt_desp_init(void)
{
    verbose_ccputs("idt descriptor is initing as expected...\n"); // Debugging message
    for (uint16_t i = 0; i < IDT_DESC_CNT; i++)
    {
        make_idt(&idt_table[i], IDT_DESC_ATTR_DPL0, asm_intr_vector_table[i]); // Initialize each entry
    }
    make_idt(&idt_table[IDT_DESC_CNT - 1], IDT_DESC_ATTR_DPL3,
        syscall_handler); // Set up the syscall interrupt handler
    verbose_ccputs("idt descriptor is initing done!\n"); // Debugging message
}

看到我们专门给syscall注册的中断嘛,注意了,我们必须要注册的是三级的DPL,因为这个是用户触发的!不然,我们则在 3 级环境下执行 int 指令会产生 GP 异常。

下一步,就是实现的syscall的汇编, 请看interrupt.S的汇编新加代码

;;;;;;;;;;;;;;;;   Interrupt 0x80   ;;;;;;;;;;;;;;;;
[bits 32]
; specific for 0x80, we don't need to mappings to the that end!
extern syscall_table
section .text
global syscall_handler
syscall_handler:
; 1. Save the context environment.
   push 0                 ; Push 0 to unify the stack format.

   push ds
   push es
   push fs
   push gs
   pushad                 ; The PUSHAD instruction pushes 32-bit registers in the order:
                          ; EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI.
                 
   push 0x80              ; Push 0x80 here to maintain a unified stack format.

; 2. Pass parameters to the syscall sub-function.
   push edx               ; The third parameter in the syscall.
   push ecx               ; The second parameter in the syscall.
   push ebx               ; The first parameter in the syscall.

; 3. Call the sub-function handler.
   call [syscall_table + eax * 4]  ; The compiler will match the correct number of parameters in the stack based on the C function declaration.
   add esp, 12            ; Skip the three parameters above.

; 4. Store the return value of the call in the current kernel stack's EAX position.
   mov [esp + 8 * 4], eax   
   jmp intr_exit          ; Return via intr_exit, restoring the context.

都到这里了,大家也就是老油条了。没啥好说的,实际上就是依照eax的系数跳函数表执行,然后回来之后将我们的返回值状态压栈。

syscall_table是我们之后定义的,所以并不着急。

添加adapter

#include "include/user/syscall/syscall.h"

/* System call with no parameters */
#define _syscall0(NUMBER)                                                  \
    ({                                                                     \
        int retval;                                                        \
        asm volatile("int $0x80" : "=a"(retval) : "a"(NUMBER) : "memory"); \
        retval;                                                            \
    })

/* System call with one parameter */
#define _syscall1(NUMBER, ARG1)               \
    ({                                        \
        int retval;                           \
        asm volatile("int $0x80"              \
                     : "=a"(retval)           \
                     : "a"(NUMBER), "b"(ARG1) \
                     : "memory");             \
        retval;                               \
    })

/* System call with two parameters */
#define _syscall2(NUMBER, ARG1, ARG2)                    \
    ({                                                   \
        int retval;                                      \
        asm volatile("int $0x80"                         \
                     : "=a"(retval)                      \
                     : "a"(NUMBER), "b"(ARG1), "c"(ARG2) \
                     : "memory");                        \
        retval;                                          \
    })

/* System call with three parameters */
#define _syscall3(NUMBER, ARG1, ARG2, ARG3)                         \
    ({                                                              \
        int retval;                                                 \
        asm volatile("int $0x80"                                    \
                     : "=a"(retval)                                 \
                     : "a"(NUMBER), "b"(ARG1), "c"(ARG2), "d"(ARG3) \
                     : "memory");                                   \
        retval;                                                     \
    })

我们采用嵌入式的汇编,实际上,就是将我们eax的返回值放到retval上,这里是使用了GCC特有的GNU-C扩展,那就是表达式的值是最后一个表达式的值,这里就是我们的retval了,他从EAX来,我们上面的汇编也分析了,我们存放着调用的状态参量

需要一个syscall_table来装载系统服务表——以getpid为例子

我们还差syscall_table没有做,这个是interrupt.S中要求的,我们,来看看syscall_init.c文件中,我写了啥:

#include "include/thread/thread.h"
#include "include/user/syscall/syscall.h"
#include "include/library/ccos_print.h"
#include "include/user/syscall/syscall_init.h"

#define SYSCALL_SUM_NR (32) // Number of system calls
typedef void *syscall;
syscall syscall_table[SYSCALL_SUM_NR]; // System call table to hold the addresses of
                                   // system call functions

/* Return the pid of the current task */
uint32_t sys_getpid(void) {
    return current_thread()->pid; // Retrieve the PID of the running thread
}

/* Initialize the system call table */
void syscall_init(void) {
    verbose_ccputs("syscall_init start\n"); // Logging the start of syscall initialization
    /* Set the system call table entries for various system call numbers */
    syscall_table[SYS_GETPID] = sys_getpid; // Get process ID
    verbose_ccputs("syscall_init done\n"); // Logging the completion of syscall
                                          // initialization
}

哦,麻烦了,我们还没有进程pid呢,需要稍微的魔改一下我们的TaskStruct。PID相当于TaskStruct的轻量标识符,我们使用这个来完成我们对线程的标识,这样的话在用户态传递线程就会显得十分的方便。

/**
 * @brief Task control block structure.
 *
 * This structure represents a thread and stores its execution context.
 */
typedef struct __cctaskstruct
{
    uint32_t *self_kstack;         // Kernel stack pointer for the thread
    pid_t pid;                     // Process ID
    TaskStatus status;             // Current status of the thread
    char name[TASK_NAME_ARRAY_SZ]; // Thread name
    uint8_t priority;              // Thread priority level
    uint8_t ticks;
    uint32_t elapsed_ticks;
    list_elem general_tag;
    list_elem all_list_tag;
    uint32_t *pg_dir;
    VirtualAddressMappings  userprog_vaddr;
    uint32_t stack_magic; // Magic value for stack overflow detection
} TaskStruct;

我们添加了一个新成员:pid,然后,为了保证线程安全,我们采用带锁的分配:

CCLocker pid_lock;  // Lock for pid allocation
...
static pid_t allocate_pid(void) { 
    static pid_t next_pid = 0; 
    lock_acquire(&pid_lock); 
    next_pid++; 
    lock_release(&pid_lock); 
    return next_pid; 
} 

/**
 * @brief Initialize a thread structure with default values.
 * 
 * This function sets up a new thread, including its name, status, priority,
 * stack, and other necessary fields.
 * 
 * @param pthread Pointer to the thread structure.
 * @param name Name of the thread.
 * @param priority Priority of the thread.
 */
void init_thread(TaskStruct *pthread, char *name, int priority)
{
    k_memset(pthread, 0, sizeof(TaskStruct)); // Clear the thread structure
    k_strcpy(pthread->name, name); // Set thread name
    pthread->pid = allocate_pid();
    if (pthread == main_thread)
    {
        pthread->status = TASK_RUNNING; // Main thread is always running
    }
    else
    {
        pthread->status = TASK_READY; // Other threads start as ready
    }

    pthread->priority = priority; // Set thread priority
    pthread->ticks = priority; // Set time slice based on priority
    pthread->elapsed_ticks = 0; // Reset elapsed ticks
    pthread->pg_dir = NULL; // No separate page directory for threads
    pthread->self_kstack = (uint32_t *)((uint32_t)pthread + PG_SIZE); // Set initial stack pointer
    pthread->stack_magic = TASK_MAGIC; // Set stack magic number for stack overflow detection
}

/* Initialize the thread subsystem */
void thread_subsystem_init(void)
{
    ccos_puts("Thread SubSystem initing...\n");
    
    // Initialize the ready and all thread lists
    list_init(&thread_ready_list);
    list_init(&thread_all_list);
    lock_init(&pid_lock);
    // Create the main thread
    make_main_thread();
    
    ccos_puts("Thread SubSystem init done\n");
}

就是这样的。allocate_pid简单的对我们的线程pid分配做递增分配,在创建线程的时候调用allocate_pid获取线程的pid,以及在线程子系统初始化的初始化一下锁,不然会出问题。

当然,还差一些事情要补充:

getpid是用户请求我们的pid用的。他直接将需求转发到我们的syscall上

/* Returns the current task's PID */
uint32_t getpid() {
    return _syscall0(SYS_GETPID);
}

对接的就是我们在syscall_init中写的sys_getpid上

/* Return the pid of the current task */
uint32_t sys_getpid(void) {
    return current_thread()->pid; // Retrieve the PID of the running thread
}

补充一下头文件:

syscall_init.h

#ifndef __USERPROG_SYSCALLINIT_H
#define __USERPROG_SYSCALLINIT_H
#include "include/library/types.h"
void syscall_init(void);
uint32_t sys_getpid(void);
#endif

syscall.h

#ifndef USR_SYSCALL_NR
#define USR_SYSCALL_NR
#include "include/library/types.h"
typedef enum {
    SYS_GETPID
}Syscall_NR;

uint32_t getpid(void);

#endif

下面写一个测试文件试试水:

#include "include/device/console_tty.h"
#include "include/kernel/init.h"
#include "include/library/kernel_assert.h"
#include "include/thread/thread.h"
#include "include/user/process/process.h"
#include "include/user/syscall/syscall.h"
#include "include/user/syscall/syscall_init.h"
void thread_a(void *args);
void thread_b(void *args);
void u_prog_a(void);
void u_prog_b(void);
int prog_a_pid = 0, prog_b_pid = 0; 

int main(void)
{
    init_all();
    create_process(u_prog_a, "user_prog_a");
    create_process(u_prog_b, "user_prog_b");
    interrupt_enabled();
    console_ccos_puts("main_pid: 0x");
    console__ccos_display_int(sys_getpid());
    console__ccos_putchar('\n');
    thread_start("k_thread_a", 31, thread_a, "In thread A:");
    thread_start("k_thread_b", 31, thread_b, "In thread B:");
    
    while (1)
    {
    }
}

void thread_a(void *arg)
{
    char* para = arg;
    (void)para;
    console_ccos_puts("thread a pid: 0x");
    console__ccos_display_int(sys_getpid());
    console__ccos_putchar('\n');
    console_ccos_puts("prog_a_pid:0x");
    console__ccos_display_int(prog_a_pid);
    console__ccos_putchar('\n');
    while (1);
}
void thread_b(void *arg)
{
    char* para = arg;
    (void)para;
    console_ccos_puts("thread b pid: 0x");
    console__ccos_display_int(sys_getpid());
    console__ccos_putchar('\n');
    console_ccos_puts("prog_b_pid:0x");
    console__ccos_display_int(prog_b_pid);
    console__ccos_putchar('\n');
    while (1);
}

void u_prog_a(void)
{
    prog_a_pid = getpid(); 
    while (1);
}

void u_prog_b(void) { 
    prog_b_pid = getpid();
    while(1);
} 

跑一下结果:

https://i-blog.csdnimg.cn/direct/cfc38dcda167447ea0ee803fdb2f2e9b.png

代码

[CCOperateSystem/Documentations/11_advanced_kernel/11.1_syscall_code at main · Charliechen114514/CCOperateSystem

https://csdnimg.cn/release/blog_editor_html/release2.3.8/ckeditor/plugins/CsdnLink/icons/icon-default.png?t=P1C7 “CCOperateSystem/Documentations/11_advanced_kernel/11.1_syscall_code at main · Charliechen114514/CCOperateSystem”)