• 孟晓冬
    2019-04-15
    这个专栏要有一定的知识储备才能学习,起码要熟悉c,数据结构,linux系统管理,否则只会一脸懵逼的进来,一脸懵逼的出去

    作者回复: 是的

     1
     173
  • why
    2019-04-15
    - glibc 将系统调用封装成更友好的接口
    - 本节解析 glibc 函数如何调用到内核的 open
    ---
    - 用户进程调用 open 函数
        - glibc 的 syscal.list 列出 glibc 函数对应的系统调用
        - glibc 的脚本 make_syscall.sh 根据 syscal.list 生成对应的宏定义(函数映射到系统调用)
        - glibc 的 syscal-template.S 使用这些宏, 定义了系统调用的调用方式(也是通过宏)
        - 其中会调用 DO_CALL (也是一个宏), 32位与 64位实现不同
    ---
    - 32位 DO_CALL (位于 i386 目录下 sysdep.h)
        - 将调用参数放入寄存器中, 由系统调用名得到系统调用号, 放入 eax
        - 执行 ENTER_KERNEL(一个宏), 对应 int $0x80 触发软中断, 进入内核
        - 调用软中断处理函数 entry_INT80_32(内核启动时, 由 trap_init() 配置)
        - entry_INT80_32 将用户态寄存器存入 pt_regs 中(保存现场以及系统调用参数), 调用 do_syscall_32_iraq_on
        - do_syscall_32_iraq_on 从 pt_regs 中取系统调用号(eax), 从系统调用表得到对应实现函数, 取 pt_regs 中存储的参数, 调用系统调用
        - entry_INT80_32 调用 INTERRUPT_RUTURN(一个宏)对应 iret 指令, 系统调用结果存在 pt_regs 的 eax 位置, 根据 pt_regs 恢复用户态进程
    ---
    - 64位 DO_CALL (位于 x86_64 目录下 sysdep.h)
        - 通过系统调用名得到系统调用号, 存入 rax; 不同中断, 执行 syscall 指令
        - MSR(特殊模块寄存器), 辅助完成某些功能(包括系统调用)
        - trap_init() 会调用 cpu_init->syscall_init 设置该寄存器
        - syscall 从 MSR 寄存器中, 拿出函数地址进行调用, 即调用 entry_SYSCALL_64
        - entry_SYSCALL_64 先保存用户态寄存器到 pt_regs 中
        - 调用 entry_SYSCALL64_slow_pat->do_syscall_64
        - do_syscall_64 从 rax 取系统调用号, 从系统调用表得到对应实现函数, 取 pt_regs 中存储的参数, 调用系统调用
        - 返回执行 USERGS_SYSRET64(一个宏), 对应执行 swapgs 和 sysretq 指令; 系统调用结果存在 pt_regs 的 ax 位置, 根据 pt_regs 恢复用户态进程
    ---
    - 系统调用表 sys_call_table
        - 32位 定义在 arch/x86/entry/syscalls/syscall_32.tbl
        - 64位 定义在 arch/x86/entry/syscalls/syscall_64.tbl
        - syscall_*.tbl 内容包括: 系统调用号, 系统调用名, 内核实现函数名(以 sys 开头)
        - 内核实现函数的声明: include/linux/syscall.h
        - 内核实现函数的实现: 某个 .c 文件, 例如 sys_open 的实现在 fs/open.c
            - .c 文件中, 以宏的方式替代函数名, 用多层宏构建函数头
        - 编译过程中, 通过 syscall_*.tbl 生成 unistd_*.h 文件
            - unistd_*.h 包含系统调用与实现函数的对应关系
        - syscall_*.h include 了 unistd_*.h 头文件, 并定义了系统调用表(数组)
    展开
    
     53
  • weihebuken
    2019-04-15
    我想问,想看懂这篇,我先需要看哪些书,或者贮备哪些知识先,真的很懵。。。

    作者回复: 主要理解过程,不必纠结代码

    
     13
  • 望天
    2019-05-23
    这些东西我觉得不必要深入每一行代码,大概过一遍,知道整体流程,宏观流程就OK了(比如上面图片的概括)。反正很多细节过一段时间也会忘。

    作者回复: 对的

    
     11
  • Sharry
    2019-05-15
    什么是系统调用?

    系统调用是操作系统提供给程序设计人员使用系统服务的接口

    系统调用流程

    Linux 提供了 glibc 库, 它封装了系统调用接口, 对上层更友好的提供服务, 系统调用最终都会通过 DO_CALL 发起, 这是一个宏定义, 其 32 位和 64 位的定义是不同的
    - 32 位系统调用
       - 用户态
          - 将请求参数保存到寄存器
          - 将系统调用名称转为系统调用号保存到寄存器 eax 中
          - 通过软中断 ENTER_KERNEL 进入内核态
       - 内核态
          - 将用户态的寄存器保存到 pt_regs 中
          - 在系统调用函数表 sys_call_table 中根据调用号找到对应的函数
          - 执行函数实现, 将返回值写入 pt_regs 的 ax 位置
          - 通过 INTERRUPT_RETURN 根据 pt_regs 恢复用户态进程

    - 64 位系统调用
       - 用户态
          - 将请求参数保存到寄存器
          - 将系统调用名称转为系统调用号保存到寄存器 rax 中
          - **通过 syscall 进入内核态**
       - 内核态
          - 将用户态的寄存器保存到 pt_regs 中
          - 在系统调用函数表 sys_call_table 中根据调用号找到对应的函数
          - 执行函数实现, 将返回值写入 pt_regs 的 ax 位置
          - **通过 sysretq 返回用户态**
    展开
    
     11
  • William
    2019-04-15
    大家可以参考glibc的源码理解,https://www.gnu.org/software/libc/started.html。 主要过程是CPU上下文切换的过程。

    作者回复: 赞

    
     8
  • 刘強
    2019-04-15
    这个专栏,源码是linux哪个版本的?

    作者回复: https://elixir.bootlin.com/linux/v4.13.16

     1
     7
  • 春明
    2019-04-15
    开始吃力了,只能排除细节,先了解几个重要阶段了。

    作者回复: 对的,就是这个方法

    
     5
  • kdb_reboot
    2019-04-15
    参数如果超过6个存在哪里?(32/64两种情况

    作者回复: 不许超过,系统调用可以查一下,没这么多参数

    
     5
  • 江山未
    2019-07-26
    宏是什么?给像我一样不懂C的人:
    1,使用命令 #define 定义宏。该命令允许把一个名称指定成任何所需的文本,例如一个常量值或者一条语句。在定义了宏之后,无论宏名称出现在源代码的何处,预处理器都会把它用定义时指定的文本替换掉。
    2,宏的名称一般使用全大写的形式。
    3,宏可以定义参数,参数列表需要使用圆括号包裹,且必须紧跟名称,中间不能有空格。
    4,使用#undef NAME取消宏的定义,从而可以重新定义或使用与宏重名的函数或变量。
    5,出现在字符串中的宏名称不会被预编译器展开。
    展开

    作者回复: 太好了,谢谢补充

    
     3
  • 张迪
    2019-06-12
    老师你好,什么是用户态什么是内核态,

    作者回复: 前几节讲的呀,保护模式

     1
     3
  • 阿恺
    2019-04-15
    老师,请教个问题,对于64位,DO_CALL在两个地方有地址,sysdeps/unix/sysv/linux/x86_64/sysdep.h:179和sysdeps/unix/x86_64/sysdep.h:26,我采用的最新的glibc的git下载。看到的和您给的代码不一样,您采用了前者的注释,后者的代码,两者使用的寄存器不一样。如何知道是通过哪个入口。sysdeps/unix/sysv/linux/x86_64/sysdep.h:179中注释写到,将系统调用号放在rax,后面的代码中的是eax,这里没有看懂。

    作者回复: 64位是rax

    
     3
  • windcaller
    2019-07-06
    够硬核的课程!
    
     2
  • 小颜
    2019-04-16
    此处仅展示32位系统调用:

    glibc大部分使用脚本封装生成代码,用到三种文件:
        1.make-syscall.sh:读取syscalls.list文件的内容,对文件的每一行进行解析。根据每一行的内容生成一个.S汇编文件,汇编文件封装了一个系统调用。文件路径:sysdeps/unix/make-syscall.sh
        2.syscall-template.S:是系统调用封装代码的模板文件。生成的.S汇编文件都调用它。文件路径:sysdeps/unix/syscall-template.S
        3.syscalls.list:是数据文件,定义了全部的系统调用信息。文件路径有多个:sysdeps/unix/syscalls.list,sysdeps/unix/sysv/linux/syscalls.list,sysdeps/unix/sysv/linux/generic/syscalls.list,sysdeps/unix/sysv/linux/i386/syscalls.list

    1.syscall-template.S生成汇编文件
        在syscall-template.S生成汇编文件中在78行有调用T_PSEUDO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS)方法,
    2.T_PSEUDO是一个宏定义,此文件会引用#include <sysdep.h>
    3.在sysdep.h文件中175行定义了函数PSEUDO:
        /* Linux takes system call arguments in registers:

            syscall number    %eax     call-clobbered /* 保存系统调用号 */
            arg 1        %ebx     call-saved
            arg 2        %ecx     call-clobbered
            arg 3        %edx     call-clobbered
            arg 4        %esi     call-saved
            arg 5        %edi     call-saved
            arg 6        %ebp     call-saved

         The stack layout upon entering the function is:

            24(%esp)    Arg# 6
            20(%esp)    Arg# 5
            16(%esp)    Arg# 4
            12(%esp)    Arg# 3
             8(%esp)    Arg# 2
             4(%esp)    Arg# 1
             (%esp)    Return address */
        #define DO_CALL(syscall_name, args)                      \
            PUSHARGS_##args                             \
            DOARGS_##args                             \
            movl $SYS_ify (syscall_name), %eax;                     \
            ENTER_KERNEL                             \
            POPARGS_##args
        在此函数中调用了DO_CALL:将 系统调用 号保存在eax寄存器,其他参数分别保存至其他寄存器
    4。然后调用ENTER_KERNEL,该宏定义在在125行:# define ENTER_KERNEL int $0x80,
    5.int $0x80是一个软中断,将会触发软中断触发函数entry_INT80_32,
    6.entry_INT80_32将用户态的一些信息保存在pt_regs,最终调用do_syscall_32_irqs_on,
    7.do_syscall_32_irqs_on函数将从eax寄存器取出系统调用号,然后根据系统调用号从系统调用表中取出索引,最终取出对应函数,参数从pt_regs中,最终调用系统调用
    8.在函数最后调用INTERRUPT_RETURN iret最终返回数据保存在pt_regs的eax中,并将pt_regs的用户态数据恢复
    展开
    
     2
  • ZYecho
    2019-09-06
    老师你好,这个地方保存的时候是保存在pt-regs结构体中,那么当中断通过iret进行返回的时候,cpu是如何知道我们的现场是存储在pt-regs结构体当中呢?
    我理解iret指令应该只会操作cpu当中的寄存器才对。

    作者回复: 栈顶呀

    
     1
  • hello
    2019-08-15
    请问老师列的代码是哪个版本的内核代码?和我看的2.6.34和5.2.8的不一样让我很慌张啊

    作者回复: 4.13

    
     1
  • nb Ack
    2019-08-15
    我都怀疑我有没上过 linux 的课

    作者回复: 有的的确当时老师不怎么讲

    
     1
  • perfect
    2019-06-20
    麻烦解释一下宏

    作者回复: the c programming language,这本书还是挺薄的

    
     1
  • 逆流的鱼
    2019-04-21
    系统调用都会导致用户态切换内核态?而纯计算的不会?

    作者回复: 系统调用都会,纯计算看算什么了,算加法不用进内核

    
     1
  • 陈锴
    2019-04-17
    有个小问题,64位内核是不是已经取消使用cs 代码寄存器 和 ds数据段寄存器了(或者说默认设为0了),也就是只采用分页而不采用分段了

    作者回复: 分段还是有,只不过是残废的状态,就像你说的一样,到了内存那一节会详细说这个问题

    
     1
我们在线,来聊聊吧