• Linux操作系统(一)系统初始化


    CPU(Central Processing Unit,中央处理器)

    在这里插入图片描述

    • 总线:用于CPU 和其他设备连接

    • CPU 它包括三个部分,运算单元、数据单元和控制单元。
      在这里插入图片描述

    • 每个进程都有一个二进制的程序放在硬盘上,再里面就是一行行的指令,会操作一些数据。

    • CPU 的控制单元里面,有一个指令指针寄存器,它里面存放的是下一条指令在内存中的地 址。控制单元会不停地将代码段的指令拿进来,先放入指令寄存器。 (指令分别是对数据 or 运算)。 数据单元里的数据寄存器,从数据段里 读到数据寄存器里,就可以参与运算了,运算单元做完运算,产生的结果会暂存在数据单元的数据寄存器里。最终,会有指令将数据写回内存中的数据段。

    • 多任务系统的一个操作 :进程切换 (A进程切换到B进程)

    • CPU 和内存来来回回传数据,靠的都是总线。一个是地址数据,也就是我想拿内存中哪个位置的数据,这类总线叫地址总线 (Address Bus);另一类是真正的数据,这类总线叫数据总线(Data Bus)

    • 地址总线的位数,决定了能访问的地址范围到底有多广。例如只有两位,那 CPU 就只能认 00,01,10,11 四个位置,超过四个位置,就区分不出来了。位数越多,能够访问的位置 就越多,能管理的内存的范围也就越广。

    • CPU 数据总线和地址总线越来越宽,处理能力越来越强。但是一直不能忘记 三点,一是标准,二是开放,三是兼容

    x86 架构

    下图是x86 86 中最经典的一款处理器 , 8086处理器
    在这里插入图片描述
    重点 : 寄存器的作用

    • 8086 处理器内部8 个 16 位的通用寄存器,也就是刚才说的 CPU 内部 的数据单元,分别是 AX、BX、CX、DX、SP、BP、SI、DI。这些寄存器主要用于在计算过 程中暂存数据。这些寄存器比较灵活,其中 AX、BX、CX、DX 可以分成两个 8 位的寄存器来使用,分别 是 AH、AL、BH、BL、CH、CL、DH、DL,其中 H 就是 High(高位),L 就是 Low(低位)的意思。
    • IP 寄存器/指令指针寄存器(Instruction Pointer Register) :指向代码段中下一条指令的位置。CPU 会根据它来不断地将指令从内存的代码段中,加载到 CPU 的指令队列中,然后交给运算单元去执行。
    • 切换进程:每个进程都分代码段和数据段,为了指向不同进程的地址空间,有四 个 16 位的段寄存器( CS、DS、SS、ES)
    • CS 就是代码段寄存器 , 通过它可以找到代码在内存中的位置
    • DS 是数据段的寄存器 , 通过它可以找到数据在内存中的位置
    • SS 是栈寄存器(Stack Register) : 数据的存取只 能从一端进行,秉承后进先出的原则,push 就是入栈,pop 就是出栈.凡是与函数调用相关的操作,都与栈紧密相关
    • 运算中需要加载内存中的数据,需要通过 DS 找到内存中的数据,加载到通用寄存器 中, 对于一个段,有一个起始的地址,而段内的具体位置,我们称为偏移 量(Offset)。 CS 和 DS 都是 16 位的,也就是说,起始地址都是 16 位的。
    • 方法:*起始地址 16+ 偏移量
    • 8086 的地址总线地址是 20 位,也就是把 CS 和 DS 中的值左 移 4 位,变成 20 位的,加上 16 位的偏移量,这样就可以得到最终 20 位的数据地址 。 20位 == 2^20^ = 1M 超过这个空间就访问 只能访问这个空间内的位置。

    32 位处理器

    在这里插入图片描述

    • 在 32 位处理器中, 有 32 根地址总线,可以访问 2^32=4G 的内存。
    • “开放” : 意味着有大量其他公司的软硬件是基于这个架构来实现的 , 存在限制
    • “兼容” : 8 个 16 位的扩展到 8 个 32 位的 。 个数不变 只扩展了空间 。
    • 段寄存器:弄了一个不上不下的 20 位的地址 , 这 样每次都要左移四位,也就意味着段的起始地址不能是任何一个地方,只是能整除 16 的地 方。
    • CS、SS、DS、ES 仍然是 16 位的。表格中的一项一项是段描述 符(Segment Descriptor)。这里面才是真正的段的起始地址。而段寄存器里面保存的是 在这个表格中的哪一项,称为选择子(Selector)。 16位以及不是以前的初始位置了。
    • 表格中的一项一项是段描述符(Segment Descriptor)。这里面才是真正的段的起始地址。而段寄存器里面保存的是 在这个表格中的哪一项,称为选择子(Selector)。
    • 实模式(Real Pattern): 从段寄存器直接拿到的段起始地址。
    • 保护模式(Protected Pattern) : 先间接地从段寄存器找到表格中 的一项,再从表格中的一项中拿到段起始地址。

    在这里插入图片描述

    系统刚刚启动 的个体户模式

    BIOS 时期

    在这里插入图片描述

    如果你自己安装过操作系统,刚启动的时候,按某个组合键,显示器会弹出一个蓝色的界 面。能够调整启动顺序的系统,就是我说的 BIOS,然后我们就可以先执行它。

    • 在主板上,有一个东西叫ROM(Read Only Memory,只读存储器)
    • 内存RAM(Random Access Memory,随机存取存储器)
    • BIOS(Basic Input and Output System,基本输入输出系统)
      在这里插入图片描述
      在 x86 系统中,将 1M 空间最上面的 0xF0000 到 0xFFFFF 这 64K 映射给 ROM, 启动电脑的时候 会进行重置 将 CS 设置为 0xFFFF,将 IP 设置为 0x0000,所以第一条指令就会指向 0xFFFF0,正是在 ROM 的范围内。在这里,有一个 JMP 命令会跳到 ROM 中做初始化工作的代码,于是,BIOS 开始进行初始化的工作。

    创业指导手册第一条,BIOS 要检查一下系统的硬件是不是都好着呢。
    创业指导手册第二条,要有个办事大厅,只不过自己就是办事员。这个时期你能提供的服务很简单,但也会有零星的客户来提要求。
    这个时候,要建立一个中断向量表和中断服务程序,因为现在你还要用键盘和鼠标,这些都 要通过中断进行的。

    在这里插入图片描述

    bootloader 时期

    • Grub2(Grand Unified Bootloader Version 2) linux的一个工具,用于系统启动。
    • grub2-mkconfig -o /boot/grub2/grub.cfg 来配置系统启动的选项

    在这里插入图片描述

    • 使用 grub2-install /dev/sda,可以将启动程序安装到相应的位置。

    • grub2 第一个要安装的就是 boot.img,它由 boot.S 编译而成,一共 512 字节,正式安装 到启动盘的第一个扇区。这个扇区通常称为MBR(Master Boot Record,主引导记录 / 扇区)。

    • 由于 512 个字节实在有限,boot.img 做不了太多的事情。它能做的最重要的一个事情就 是加载grub2 的另一个镜像 core.img(由 lzma_decompress.img、diskboot.img、kernel.img 和一系列的模块组成)。

      在这里插入图片描述

    • 如果从硬盘启动的话,这个扇区里面是 diskboot.img,对应的代码是 diskboot.S。

    • boot.img 将控制权交给 diskboot.img 后,diskboot.img 的任务就是将 core.img 的其他 部分加载进来,先是解压缩程序 lzma_decompress.img,再往下是 kernel.img,最后是 各个模块 module 对应的映像。这里需要注意,它不是 Linux 的内核,而是 grub 的内核。

    • lzma_decompress.img 对应的代码是 startup_raw.S,本来 kernel.img 是压缩过的,现 在执行的时候,需要解压缩。

    • 实模式: 1M 的地址空间 (只能满足比较小的程序)所以在真正的解压缩之前,lzma_decompress.img 做了一个重要的决定,就是调用 real_to_prot,切换到保护模式,这样就能在更大的寻址空间里面,加载更多的东西。

    从实模式切换到保护模式

    切换到保护模式要干很多工作,大部分工作都与内存的访问方式有关。
    第一项是启用分段,就是在内存里面建立段描述符表,将寄存器里面的段寄存器变成段选择 子,指向某个段描述符,这样就能实现不同进程的切换了。第二项是启动分页。能够管理的 内存变大了,就需要将内存分成相等大小的块.
    在这里插入图片描述

    内核初始化

    内核的启动从入口函数 start_kernel() 开始。在 init/main.c 文件中,start_kernel 相当于 内核的 main 函数。打开这个函数,你会发现,里面是各种各样初始化函数 XXXX_init

    在这里插入图片描述

    • 在操作系统里面,先要有个创始进程,有一行指令set_task_stack_end_magic(&init_task)。这里面有一个参数 init_task,它的定义是 struct task_struct init_task = INIT_TASK(init_task)。它是系统创建的第一个进程,我们称为0 号进程。这是唯一一个没有通过 fork 或者 kernel_thread 产生的进程,是进程列表的第一个。
    • 32位系统 trap_init(),里面设置了很多中断门(Interrupt Gate),用于处理各 种中断。其中有一个 set_system_intr_gate(IA32_SYSCALL_VECTOR, entry_INT80_32), 这是系统调用的中断门。
    • mm_init() 就是用来初始化内存管理模块
    • sched_init() 就是用于初始化调度 模块
    • vfs_caches_init() 会用来初始化基于内存的文件系统 rootfs。为了兼容各种各样的文件系统,我们需要将文件的相关数据 结构和操作抽象出来,形成一个抽象层对上提供统一的接口,这个抽象层就是 VFS(Virtual File System),虚拟文件系统。
    • 1 号进程对于操作系统来讲,有“划时代”的意义。因为它将运行一个用户进程,然后会继承很多子进程,形成一棵进程树。 有了进程也就有了权限
    • x86 提供了分层的权限机制,把区域分成了四个 Ring,越往里权限越高,越往外权限越低.

    在这里插入图片描述
    操作系统很好地利用了这个机制,将能够访问关键资源的代码放在 Ring0,我们称为内核态(Kernel Mode);将普通的程序代码放在 Ring3,我们称为用户态(User Mode)

    在这里插入图片描述

    当一个用户态的程序运行到一半,要访问一个核心资源,例如访问网卡发一个网络包,就需 要暂停当前的运行,调用系统调用,接下来就轮到内核中的代码运行了。
    首先,内核将从系统调用传过来的包,在网卡上排队,轮到的时候就发送。发送完了,系统 调用就结束了,返回用户态,让暂停运行的程序接着运行.
    在这里插入图片描述

    从内核态到用户态

    kernel_thread 的参数是一个函数 kernel_init,也就是这个进程会运行这个函数。在 kernel_init 里面,会调用 kernel_init_freeable(),里面有这样的代码:

    if (!ramdisk_execute_command)  //如果不为空  就初始化
     ramdisk_execute_command = "/init";
    
    kernel_init:
    if (ramdisk_execute_command) {
     ret = run_init_process(ramdisk_execute_command);
      ...... }
       ......
    if (!try_to_run_init_process("/sbin/init") ||
     !try_to_run_init_process("/etc/init") ||
      !try_to_run_init_process("/bin/init") ||
       !try_to_run_init_process("/bin/sh")) 
    return 0;  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 1 号进程运行的是一个文件。如果我们打开 run_init_process 函数,会发现它 调用的是 do_execve。execve 是一个系统调用,它的作 用是运行一个执行文件。加一个 do_ 的往往是内核系统调用的实现。没错,这就是一个系 统调用,它会尝试运行 ramdisk 的“/init”,或者普通文件系统上 的“/sbin/init”“/etc/init”“/bin/init”“/bin/sh”。不同版本的 Linux 会选择不同的 文件启动,但是只要有一个起来了就可以,而咱们刚才运行 init,是调用 do_execve,正是上面的过程的后半部分,从内核态执行系统调用开始。
    //do_execve->do_execveat_common->exec_binprm->search_binary_handler
    int search_binary_handler(struct linux_binprm *bprm) {
    ......
    struct linux_binfmt *fmt; ......
    retval = fmt->load_binary(bprm); ......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    我要运行一个程序,需要加载这个二进制文件,它是有一定格式的。Linux 下一个常用的格式是ELF(Executable and Linkable Format,可执行与可链接格式)。

    //二进制 格式
    static struct linux_binfmt elf_format = { 
    .module = THIS_MODULE, .load_binary
    = load_elf_binary, .load_shlib = load_elf_library,
    .core_dump = elf_core_dump,
    .min_coredump = ELF_EXEC_PAGESIZE, 
    };
    
    
    
    
    void start_thread(struct pt_regs *regs, unsigned long new_ip, unsigned long new_sp) {
    set_user_gs(regs, 0);  // register,就是寄存器
    regs->fs= 0; 
    regs->ds= __USER_DS;  //设置为用户态
    regs->es = __USER_DS; 
    regs->ss = __USER_DS; 
    regs->cs = __USER_CS; 
    regs->ip  = new_ip;  //恢复
    regs->sp  = new_sp;  // 恢复
    regs->flags= X86_EFLAGS_IF;
    force_iret(); 
    }
    EXPORT_SYMBOL_GPL(start_thread);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    **ramdisk 的作用:**内核就太大了,需要一个基于内存的文件系统,内存访问是不需要驱动的,这个就是 ramdisk。 这个时候,ramdisk 是根文件系统。ramdisk 上的 /init 会启动文件系统上的 init ,形成了用户态所有进程的祖 先

    2号进程:

    • rest_init 第二大事情就是第三个进程,就是 2 号进程。kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES) 又一次使用 kernel_thread 函数创建进程。
    • 函数名 thread 可以翻译成“线程”
    • 有多个人并 行执行不同的部分,这就叫多线程(Multithreading),如果只有一个人,那它就是这个 项目的主线程。
    • 从内核态来看,无论是进程,还是线程,我们都可以统称为任务(Task),都使用相 同的数据结构,平放在同一个链表中。
    • 函数 kthreadd,负责所有内核态的线程的调度和管理,是内核态所有线程运行的祖先。

    glibc 对系统调用的封装

    Linux 还提供了glibc 这个中介。它更熟悉系统调用的细节,并且可以封装成更加友好的接口。

    • glibc 里面的 open 函数int open(const char *pathname, int flags, mode_t mode)
    • 在 glibc 的源代码中,有个文件 syscalls.list,里面列着所有 glibc 的函数对应的系统调用。 (下图只显示 open 的)
      在这里插入图片描述
    • glibc 还有一个脚本 make-syscall.sh.可以根据上面的配置文件,对于每一个封装 好的系统调用,生成一个文件。这个文件里面定义了一些宏,例如 #define SYSCALL_NAME open
    T_PSEUDO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS) ret  //伪代码  符号 名字  参数
    T_PSEUDO_END (SYSCALL_SYMBOL) #define T_PSEUDO(SYMBOL, NAME, N)
    PSEUDO (SYMBOL, NAME, N)
    
    PSEUDO 也是一个宏
    
    #define PSEUDO(name, syscall_name, args) .text;
    ENTRY (name)
    DO_CALL (syscall_name, args); 
    cmpl $-4095, %eax;
    jae SYSCALL_ERROR_LABEL 
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 里面对于任何一个系统调用,会调用 DO_CALL。这也是一个宏,这个宏 32 位和 64 位的 定义是不一样的

    32 位系统调用过程

    在这里插入图片描述

    • ENTER_KERNEL : # define ENTER_KERNEL int $0x80 int 就是 interrupt,也就是“中断”的意思。int $0x80 就是触发一个软中断,通过它就可以陷入(trap)内核.
    set_system_intr_gate(IA32_SYSCALL_VECTOR, entry_INT80_32);  // 系统启动时的 trap_init
    
    
    ENTRY(entry_INT80_32)//接收到一个系统调用的时候,entry_INT80_32 就被调用了
            ASM_CLAC
            pushl   %eax                    /* pt_regs->orig_ax */
            SAVE_ALL pt_regs_ax=$-ENOSYS    /* save rest */ //通过 push 和 SAVE_ALL 将当前用户态的寄存器,保存在 pt_regs 结构里面。
            movl    %esp, %eax  //内核之前,保存所有的寄存
            call    do_syscall_32_irqs_on  // 调用 do_syscall_32_irqs_on
    /*
    //下面是  do_syscall_32_irqs_on
    
    static __always_inline void do_syscall_32_irqs_on(struct pt_regs *regs)  //
    {
    	struct thread_info *ti = current_thread_info();
    	unsigned int nr = (unsigned int)regs->orig_ax; // 将系统调用号从 eax 里面取出来
    ......
    	if (likely(nr < IA32_NR_syscalls)) {
    	//然后根据系统调用号,在系统调用 表中找到相应的函数进行调用,并将寄存器中保存的参数取出来,作为函数参数。
    		regs->ax = ia32_sys_call_table[nr](  //#define ia32_sys_call_table sys_call_table,系统调用就是放在这个表里 面。
    			(unsigned int)regs->bx, (unsigned int)regs->cx,
    			(unsigned int)regs->dx, (unsigned int)regs->si,
    			(unsigned int)regs->di, (unsigned int)regs->bp);
    	}
    	syscall_return_slowpath(regs);
    }
    
    */
    .Lsyscall_32_done:
    ......
    .Lirq_return:
    	INTERRUPT_RETURN  //紧接着调用的是 INTERRUPT_RETURN,我们能够找到它的定义,也就是 iret。
    	//#define INTERRUPT_RETURN iret
    //iret 指令将原来用户态保存的现场恢复回来,包含代码段、指令指针寄存器等。这时候用户 态进程恢复执行。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    32 位的系统调用的执行过程
    在这里插入图片描述

    64位

    x86_64 下的 sysdep.h 文件

    /* The Linux/x86-64 kernel expects the system call parameters in
       registers according to the following table:
        syscall number	rax
        arg 1		rdi
        arg 2		rsi
        arg 3		rdx
        arg 4		r10
        arg 5		r8
        arg 6		r9
    ......
    */
    #define DO_CALL(syscall_name, args)					      \
      lea SYS_ify (syscall_name), %rax;					      \
      syscall
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    与32位的区别 放到寄存器 rax

    • 在系统初始化的时候,trap_init 除了初始化上面的中断模式,这里面还会调用 cpu_init>syscall_init。这里面有这样的代码:`
    wrmsrl(MSR_LSTAR, (unsigned long)entry_SYSCALL_64);  
    
    rdmsr 和 wrmsr 是用来读写特殊模块寄存器 的
    MSR_LSTAR  一个特殊的寄存器专门用来做系统调用
    
    entry_SYSCALL_64 如下
    ENTRY(entry_SYSCALL_64)
            /* Construct struct pt_regs on stack */
    //这里先保存了很多寄存器到 pt_regs 结构里面,例如用户态的代码段、数据段、保存参数 的寄存器
            pushq   $__USER_DS                      /* pt_regs->ss */ 
            pushq   PER_CPU_VAR(rsp_scratch)        /* pt_regs->sp */
            pushq   %r11                            /* pt_regs->flags */
            pushq   $__USER_CS                      /* pt_regs->cs */
            pushq   %rcx                            /* pt_regs->ip */
            pushq   %rax                            /* pt_regs->orig_ax */
            pushq   %rdi                            /* pt_regs->di */
            pushq   %rsi                            /* pt_regs->si */
            pushq   %rdx                            /* pt_regs->dx */
            pushq   %rcx                            /* pt_regs->cx */
            pushq   $-ENOSYS                        /* pt_regs->ax */
            pushq   %r8                             /* pt_regs->r8 */
            pushq   %r9                             /* pt_regs->r9 */
            pushq   %r10                            /* pt_regs->r10 */
            pushq   %r11                            /* pt_regs->r11 */
            sub     $(6*8), %rsp                    /* pt_regs->bp, bx, r12-15 not saved */
            movq    PER_CPU_VAR(current_task), %r11
            testl   $_TIF_WORK_SYSCALL_ENTRY|_TIF_ALLWORK_MASK, TASK_TI_flags(%r11)
            jnz     entry_SYSCALL64_slow_path //然后调用 entry_SYSCALL64_slow_pat->do_syscall_64  如下
    ......
    /*调用 entry_SYSCALL64_slow_pat->do_syscall_64   和 32位的差不多
    __visible void do_syscall_64(struct pt_regs *regs)
    {
            struct thread_info *ti = current_thread_info();
            unsigned long nr = regs->orig_ax;  //从 rax 里面拿出系统调用号
    ......
            if (likely((nr & __SYSCALL_MASK) < NR_syscalls)) {
                    regs->ax = sys_call_table[nr & __SYSCALL_MASK](  //查表
                            regs->di, regs->si, regs->dx,
                            regs->r10, regs->r8, regs->r9);  //这些参数所对应的寄存器,和 Linux 的注释又是一样的。
            }
            syscall_return_slowpath(regs);
    }
    */
    entry_SYSCALL64_slow_path:
            /* IRQs are off. */
            SAVE_EXTRA_REGS
            movq    %rsp, %rdi
            call    do_syscall_64           /* returns with IRQs disabled */
    return_from_SYSCALL_64:
    	RESTORE_EXTRA_REGS
    	TRACE_IRQS_IRETQ
    	movq	RCX(%rsp), %rcx
    	movq	RIP(%rsp), %r11
        movq	R11(%rsp), %r11
    ......
    syscall_return_via_sysret:
    	/* rcx and r11 are already restored (see code above) */
    	RESTORE_C_REGS_EXCEPT_RCX_R11
    	movq	RSP(%rsp), %rsp
    	USERGS_SYSRET64  //64 位的系统调用返回的时候,执行的是 USERGS_SYSRET64
    	//#define USERGS_SYSRET64 swapgs; sysretq; 返回用户态的指令变成了 sysretq。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    在这里插入图片描述

    系统调用表

    系统调用的方式,都是最终到了系统调用表,但是到底调用内核的什么函数?
    32 位的系统调用表定义在面 arch/x86/entry/syscalls/syscall_32.tbl 文件里。例如 open 是这样定义的:

    5 i386 open sys_open compat_sys_open
    
    • 1

    64 位的系统调用定义在另一个文件 arch/x86/entry/syscalls/syscall_64.tbl 里。例如 open 是这样定义的

    2 common open sys_open
    
    • 1

    第一列的数字是系统调用号。32 位和 64 位的系统调用号是不一样的。第三列是系统调用的名字,第四列是系统调用在内核的实现函数。不
    过,它们都是以 sys_ 开头

    • 系统调用在内核中的实现函数要有一个声明。声明往往在 include/linux/syscalls.h 文件 中。
    asmlinkage long sys_open(const char __user *filename, int flags, umode_t mode);
    
    • 1
    • 真正的实现这个系统调用,一般在一个.c 文件里面,例如 sys_open 的实现在 fs/open.c 里面
    SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode) {
    //SYSCALL_DEFINE3 是一个宏系统调用最多六个参数 (3也是宏的参数),根据参数的数目选择宏
            if (force_o_largefile())
                    flags |= O_LARGEFILE;
            return do_sys_open(AT_FDCWD, filename, flags, mode);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在编译的过程中,需要根据 syscall_32.tbl 和 syscall_64.tbl 生成自己的 unistd_32.h 和 unistd_64.h。生成方式在 arch/x86/entry/syscalls/Makefile 中。
    这里面会使用两个脚本,其中第一个脚本 arch/x86/entry/syscalls/syscallhdr.sh,会在文 件中生成 #define __NR_open;第二个脚本 arch/x86/entry/syscalls/syscalltbl.sh,会在 文件中生成 __SYSCALL(__NR_open, sys_open)。这样,unistd_32.h 和 unistd_64.h 是对 应的系统调用号和系统调用实现函数之间的对应关系。

    在这里插入图片描述
    64位的系统调用 + 内核态和用户态的模式切换
    在这里插入图片描述

    在这里插入图片描述

  • 相关阅读:
    Qt 从 QTransform 逆向解出 Translate/Scale/Rotate(平移/缩放/旋转)分析
    反射及暴力反射
    K8s复习笔记12--Ingress/Egress实验9则
    工程机械市场逆风中,中联重科路向何方?
    SwiftUI 如何保证 Text 中字符数量相等的字符串显示宽度一定相同?
    【负荷预测】基于双向LSTM模型进行电力需求预测(Matlab代码实现)
    java自学第三天
    基于单片机+物联网控制的校园空气净化计划系统设计
    Maven 使用
    Python获取阿里巴巴商品详情api
  • 原文地址:https://blog.csdn.net/weixin_49486457/article/details/125323449