什么是系统调用

系统调用 (在 Linux 中常称为 syscalls ) 是应用程序访问硬件设备之间的桥梁。

系统调用层为用户空间提供一种硬件的抽象接口,使得用户不用关注设备的具体信息,同时系统调用保证了系统的稳定和安全。

在 Linux 中,除了异常和陷入外,系统调用是用户空间访问内核的唯一手段。

实际上,其他的像设备文件和 /proc 之类的方式,最终也还是要通过系统调用的方式进行访问。

系统调用号

在 Linux 中,每个系统调用被赋予一个系统调用号。通过这个独一无二的调用号就可以关联具体的系统调用。

在用户空间执行一个系统调用时候,这个系统调用号就被用来指明到底是要执行哪个系统调用,进程不会提及系统调用的名称。

系统调用号一旦分配就不能再有任何改变,否则编译好的应用程序就会崩溃。

在内核中通过系统调用表来记录所有已注册过的系统调用的列表,存储在 sys_call_table 中。它与体系结构有关,一般在 entry.s 中定义。这个表中为每一个有效的系统调用制定了一个唯一的系统调用号。

rch\x86\kernel\syscall_table_32.S

ENTRY(sys_call_table)
.long sys_restart_syscall /* 0 - old “setup()” system call, used for restarting /
.long sys_exit
.long sys_fork
.long sys_read
.long sys_write
.long sys_open /
5 /
.long sys_close
.long sys_waitpid
.long sys_creat
.long sys_link
.long sys_unlink /
10 /
.long sys_execve
.long sys_chdir
.long sys_time
.long sys_mknod
.long sys_chmod /
15 */

...

应用程序依靠软中断的方式通知内核要进行系统调用。

该通知内核的机制通过引发一个异常来促使系统切换到内核态去执行异常处理程序。此时的异常处理程序实际上就是系统调用处理程序。

在 x86 系统上软中断由 int $0X80 指令产生(int 指令是程序用来显式声明软中断的,故而所谓的“基于int指令的系统调用”便是来源于此)。这条指令会触发一个异常导致系统切换到内核态并执行第 128 号异常处理程序,而该程序正是系统调用的处理程序,名为 system_call()。它与硬件体系结构紧密相关。

在 x86 上,系统调用号是通过 eax 寄存器传递给内核的。在陷入内核之前,用户空间就把相应的系统调用号放到 eax 中了。这样系统调用程序一旦运行,就可以从 eax 获取系统调用号。

system_call() 通过将给定的系统调用号与 NR_syscalls 做比较来检查其有效性。若它大于或等于 NR_syscalls,该函数就返回 -ENOSYS。否则,就执行相应的系统调用。

call *sys_call_table(, %eax, 4)

由于系统调用表中的表项是以 32 位(4字节)类型存放的,所以内核需要将给定的系统调用号乘以 4,然后用所得的结果在该表中查询其位置。

如以 read()调用过程如下:

参数传递

由于用户空间和内核空间使用不同的栈空间,因此系统调用的参数需要使用寄存器进行传递。在 x86 系统上,ebx、ecx、edx、esi 和 edi 按照顺序存放前5个参数。若参数大于或等于6个,需要用一个单独的寄存器存放指向所有这些参数在用户空间地址的指针。

给用户空间发的返回值也通过寄存器传递。在 x86 系统上,它存放在 eax 寄存器中。若系统调用产生大量的数据不能通过返回机制传递给用户进程,那必须通过指定的内存区交换该数据。当然,该内存区必须在用户空间中,使得用户应用层序能够访问。

在内核访问自身的内存区时,虚拟地址和物理内存页之间的映射总是存在的。但用户空间中的情况有所不同,页可能被换出,甚至可能尚未分配物理内存页。

因而内核不能简单的反引用用户空间的指针,而必须采用特定的函数,确保目标内存区已经在物理内存中,为确保这种约定,用户空间指针通过_user属性标记,以支持 C check tools 对源代码的自动化检查。

大多数情况下,用户在用户空间和内核空间之间复制数据的函数使用copy_to_user() 和 copy_from_user(),但还有更多的变体。

注意 copy_to_user() 和 copy_from_user() 都有可能引起阻塞。当包含用户数据的页被换出到硬盘上而不是在物理内存上的时候,这种情况就会发生。此时,进程就会休眠,直到缺页处理程序将该页从硬盘重新换回物理内存。

初始化

Linux 内核在启动过程中会对向量中断进行初始化,该初始化 trap_init 中会对系统调用设置中断号,SYSCALL_VECTOR 就是 0x80 中断号。

void __init trap_init(void)
{
int i;

...set_system_gate(SYSCALL_VECTOR,&system_call);...
cpu_init();trap_init_hook();

}

而 system_call 具体实现在 arch\x86\kernel\entry_32.S 中。

系统调用过程

当用户进程调用一个系统调用时,用户进程会触发一个中断向量号为 0x80 的软中断,最终会执行 system_call 函数。

在实际执行中断向量表中的第 0x80 号所对应的 system_call 函数前,CPU 首先还要进行栈的切换。在 Linux 中,用户态和内核态使用的是不同的栈,两者各自负责各自的函数调用,互不干扰。

在int指令中,CPU 除了切入内核态之外,还要找到当前进程的内核栈,在内核栈中依次压入当前进程用户态的寄存器 SS(Stack Segment,堆栈段寄存器)、ESP、EFLAGS、CS(Code Segment,代码段寄存器 )、EIP。这些中断指令自动地由硬件完成。

当然,当内核从系统调用中返回的时候,需要调用 iret 指令来回到用户态,iret 指令则从内核栈中弹出 SS、ESP、EFLAGS、CS、EIP 的值,使得栈恢复到用户态的状态。

当 CPU 在 int 指令中切换了栈后,程序通过 0x80 从中断向量表中获取中断处理程序,也即是 system_call(),该函数在 arch\x86\kernel\entry_32.S 中。

# system call handler stub

ENTRY(system_call) #执行int 0x80的下一条指令
RING0_INT_FRAME # can’t unwind into user space anyway
pushl %eax # save orig_eax
CFI_ADJUST_CFA_OFFSET 4
SAVE_ALL #将所有寄存器的值在内核态栈上保存,也就是所谓的保存现场

# 通过宏获取当前进程的thread_info 结构地址 #define GET_THREAD_INFO(reg)  movl $-THREAD_SIZE, reg; andl %esp, reg
GET_THREAD_INFO(%ebp) # ebp用于存放当前进程thread_info结构的地址# system call tracing in operation / emulation/* Note, _TIF_SECCOMP is bit number 8, and so it needs testw and not testb */
#检测当前进程是否被跟踪,也即是_TIF_SYSCALL_TRACE、_TIF_SYSCALL_AUDIT 被置1,若发生被跟踪情况则转向相应的处理命令处
testw $(_TIF_SYSCALL_EMU|_TIF_SYSCALL_TRACE|_TIF_SECCOMP|_TIF_SYSCALL_AUDIT),TI_flags(%ebp)
jnz syscall_trace_entry
# 对用户进程传递过来的系统调用号进行合法性检查,若不合法,则跳到syscall_badsys处
cmpl $(nr_syscalls), %eax
jae syscall_badsys  # 不合法,跳入到异常处理

#若系统调用号合法,则跳入到相应系统调用号所对应的服务历程当中,也即是从 sys_call_table表中找到相应的入口函数。
syscall_call:
call sys_call_table(,%eax,4) #由于表中的表项占4个字节,因此获取服务历程的方法为:sys_call_table表基地址+%eax系统调用号4
# %eax保存的是当前系统调用返回值,把该返回值保存在曾保存用户态eax寄存器值的那个栈单元位置上。用户态就可以从eax寄存器中获取系统调用的返回码了。
movl %eax,PT_EAX(%esp) # store the return value
syscall_exit:
LOCKDEP_SYS_EXIT
DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don’t miss an interrupt
# setting need_resched or sigpending
# between sampling and the iret
TRACE_IRQS_OFF
testl $TF_MASK,PT_EFLAGS(%esp) # If tracing set singlestep flag on exit
jz no_singlestep
orl $_TIF_SINGLESTEP,TI_flags(%ebp)
no_singlestep:
movl TI_flags(%ebp), %ecx
testw $_TIF_ALLWORK_MASK, %cx # 检查当前进程是否还有工作没有完成,若有,跳到 syscall_exit_work
jne syscall_exit_work #进程调度时机

restore_all:
movl PT_EFLAGS(%esp), %eax # mix EFLAGS, SS and CS
# Warning: PT_OLDSS(%esp) contains the wrong/random values if we
# are returning to the kernel.
# See comments in process.c:copy_thread() for details.
movb PT_OLDSS(%esp), %ah
movb PT_CS(%esp), %al
andl $(VM_MASK | (SEGMENT_TI_MASK << 8) | SEGMENT_RPL_MASK), %eax
cmpl $((SEGMENT_LDT << 8) | USER_RPL), %eax
CFI_REMEMBER_STATE
je ldt_ss # returning to user-space with LDT SS
restore_nocheck:
TRACE_IRQS_IRET
restore_nocheck_notrace:
RESTORE_REGS # 恢复了save_all保存的所有寄存器的值
addl $4, %esp # skip orig_eax/error_code
CFI_ADJUST_CFA_OFFSET -4
1: INTERRUPT_RETURN #中断返回 相当于iret,程序将回到用户态继续执行
.section .fixup,“ax”
iret_exc:
pushl $0 # no error code
pushl $do_iret_error
jmp error_code
.previous
.section __ex_table,“a”
.align 4
.long 1b,iret_exc
.previous

CFI_RESTORE_STATE

ldt_ss:
larl PT_OLDSS(%esp), %eax
jnz restore_nocheck
testl $0x00400000, %eax # returning to 32bit stack?
jnz restore_nocheck # allright, normal return

#ifdef CONFIG_PARAVIRT
/*
* The kernel can’t run on a non-flat stack if paravirt mode
* is active. Rather than try to fixup the high bits of
* ESP, bypass this code entirely. This may break DOSemu
* and/or Wine support in a paravirt VM, although the option
* is still available to implement the setting of the high
* 16-bits in the INTERRUPT_RETURN paravirt-op.
*/
cmpl $0, pv_info+PARAVIRT_enabled
jne restore_nocheck
#endif

/* If returning to userspace with 16bit stack,* try to fix the higher word of ESP, as the CPU* won't restore it.* This is an "official" bug of all the x86-compatible* CPUs, which we can try to work around to make* dosemu and wine happy. */
movl PT_OLDESP(%esp), %eax
movl %esp, %edx
call patch_espfix_desc
pushl $__ESPFIX_SS
CFI_ADJUST_CFA_OFFSET 4
pushl %eax
CFI_ADJUST_CFA_OFFSET 4
DISABLE_INTERRUPTS(CLBR_EAX)
TRACE_IRQS_OFF
lss (%esp), %esp
CFI_ADJUST_CFA_OFFSET -8
jmp restore_nocheck
CFI_ENDPROC

ENDPROC(system_call)

# perform work that needs to be done immediately before resumption
ALIGN
RING0_PTREGS_FRAME      # can't unwind into user space anyway

work_pending:
testb $_TIF_NEED_RESCHED, %cl #判断是否需要进程调度
jz work_notifysig
work_resched:
call schedule #执行进程调度
LOCKDEP_SYS_EXIT
DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don’t miss an interrupt
# setting need_resched or sigpending
# between sampling and the iret
TRACE_IRQS_OFF # 关闭中断跟踪
movl TI_flags(%ebp), %ecx # 检测是否还有其他任务
andl $_TIF_WORK_MASK, %ecx # is there any work to be done other
# than syscall tracing?
jz restore_all # 返回restore_all
testb $_TIF_NEED_RESCHED, %cl
jnz work_resched

work_notifysig: # 处理未决信号集 # deal with pending signals and
# notify-resume requests
#ifdef CONFIG_VM86
testl $VM_MASK, PT_EFLAGS(%esp)
movl %esp, %eax
jne work_notifysig_v86 # returning to kernel-space or
# vm86-space
xorl %edx, %edx
call do_notify_resume
jmp resume_userspace_sig

ALIGN

work_notifysig_v86:
pushl %ecx # save ti_flags for do_notify_resume
CFI_ADJUST_CFA_OFFSET 4
call save_v86_state # %eax contains pt_regs pointer
popl %ecx
CFI_ADJUST_CFA_OFFSET -4
movl %eax, %esp
#else
movl %esp, %eax
#endif
xorl %edx, %edx
call do_notify_resume # 将信号传递到进程
jmp resume_userspace_sig
END(work_pending)

# perform syscall exit tracing
ALIGN

syscall_trace_entry:
movl $-ENOSYS,PT_EAX(%esp)
movl %esp, %eax
xorl %edx,%edx
call do_syscall_trace
cmpl $0, %eax
jne resume_userspace # ret != 0 -> running under PTRACE_SYSEMU,
# so must skip actual syscall
movl PT_ORIG_EAX(%esp), %eax
cmpl $(nr_syscalls), %eax
jnae syscall_call
jmp syscall_exit
END(syscall_trace_entry)

# perform syscall exit tracing
ALIGN

syscall_exit_work:
testb $(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT|_TIF_SINGLESTEP), %cl
jz work_pending
TRACE_IRQS_ON
ENABLE_INTERRUPTS(CLBR_ANY) # could let do_syscall_trace() call
# schedule() instead
movl %esp, %eax
movl $1, %edx
call do_syscall_trace
jmp resume_userspace # 恢复用户空间
END(syscall_exit_work)
CFI_ENDPROC

RING0_INT_FRAME         # can't unwind into user space anyway

syscall_fault:
pushl %eax # save orig_eax
CFI_ADJUST_CFA_OFFSET 4
SAVE_ALL
GET_THREAD_INFO(%ebp)
movl $-EFAULT,PT_EAX(%esp)
jmp resume_userspace
END(syscall_fault)

在执行系统调用前会把寄存器中保存的用户态信息保存到内核栈中,然后通过系统调用号找到从 sys_call_table 中找到具体的系统调用入口,执行系统调用。系统调用执行完后,把返回值保存到 eax% 中,用户态程序可以从 eax% 中获取系统调用结果。

对于宏 SAVE_ALL 来说,会把将寄存器的值压入堆栈当中,压入顺序对应struct pt_regs ,出栈时调用 RESTORE_REGS 恢复 SAVE_ALL 压入的寄存器的值。

#define SAVE_ALL
cld;
pushl %fs;
pushl %es;
pushl %ds;
pushl %eax;
pushl %ebp;
pushl %edi;
pushl %esi;
pushl %edx;
pushl %ecx;
pushl %ebx;
movl $(__USER_DS), %edx;
movl %edx, %ds;
movl %edx, %es;
movl $(__KERNEL_PERCPU), %edx;
movl %edx, %fs

struct pt_regs {
long ebx;
long ecx;
long edx;
long esi;
long edi;
long ebp;
long eax;
int xds;
int xes;
int xfs;
/* int xgs; */
long orig_eax;
long eip;
int xcs;
long eflags;
long esp;
int xss;
};

恢复现场的宏 RESTORE_REGS ,中断返回时,恢复相关寄存器的内容是通过RESTORE_REGS 宏完成的,同时也可以看出,SAVE_ALL 和 RESTORE_REGS 遥相呼应,当执行 iret指令时,内核栈又恢复了进入中断前的状态,并使 CPU 从中断中返回。

#define RESTORE_INT_REGS
popl %ebx;
popl %ecx;
popl %edx;
popl %esi;
popl %edi;
popl %ebp;
popl %eax; \

#define RESTORE_REGS
RESTORE_INT_REGS;
1: popl %ds; \

2: popl %es; \

3: popl %fs; \

具体系统调用流程如下:

附:

SS(Stack Segment)为堆栈段寄存器,存放栈顶的段地址

SP(Stack Pointer) 为堆栈指针寄存器, 存放栈顶的偏移地址

任意时刻,SS:SP指向栈顶元素。

关系如下图

原文链接
Linux 系统调用的执行过程

公众号 Linux码农 推荐阅读

Linux ‘网络配置’ 和 ‘故障排除’ 命令总结
Linux 进程管理之基础知识

你需要了解的55个网络概念
Centos7 开启 iptables 日志

memcache 多线程模型

linux ulimit 调优

Linux GDB的实现原理

服务端 TCP 连接的 TIME_WAIT 过多问题的分析与解决

60秒内对 Linux 进行性能诊断

Linux 下的资源限制

你需要了解的55个网络概念

常见的限流方式之漏桶算法

常见的限流方式之计数器滑动窗口算法

应该知道的LINUX技巧

Linux 可执行文件程序载入和执行过程

进程间通信(IPC) 系列 | mmap

一文讲懂什么是vlan、三层交换机、网关、DNS、子网掩码、MAC地址

关于 TCP/IP,必知必会的十个问题

高性能定时器策略之时间轮定时器算法

关注公众号 Linux码农 获取更多干货

Linux 系统调用的执行过程相关推荐

  1. 操作系统原理,系统调用,系统调用与库函数API等函数之间的调用关系,功能与机制设计,系统调用的执行过程与Linux系统调用执行示例,不同操作系统下的PCB

    操作系统原理,系统调用,功能与机制设计,系统调用的执行过程与Linux系统调用执行示例,不同操作系统下的PCB 一.系统调用:操作系统功能调用,用户在编程时可以调用的操作系统功能. 1.系统调用是操作 ...

  2. Java程序员需要掌握的计算机底层知识(二):操作系统、内核、用户态与内核态、系统调用的执行过程

    操作系统 启动过程 通电 -> bios uefi 工作 -> 自检 -> 到硬盘固定位置加载bootloader -> 读取可配置信息 -> CMOS CMOS 用来存 ...

  3. linux进程上下文切换的具体过程,Linux实验三 结合中断上下文切换和进程上下文切换分析Linux内核一般执行过程...

    fork系统调?创建?进程,也就?个进程变成了两个进程,两个进程执?相同的代码,只是fork系统调?在?进程和?进程中的返回值不同. 打开linux-5.4.34/arch/x86/entry/sys ...

  4. linux每隔多久调度y,Linux 进程调度+Linux系统一般执行过程 笔记

    进程的调度时机与进程的切换 操作系统原理中介绍了大量进程调度算法,这些算法从实现的角度看仅仅是从运行队列中选择一个新进程,选择的过程中运用了不同的策略而已. 对于理解操作系统的工作机制,反而是进程的调 ...

  5. 1.3.3 系统调用(执行过程、访管指令、库函数与系统调用)

    文章目录 1.系统调用知识框架图 2.系统调用和库函数的区别 3.系统调用的执行过程 1.系统调用知识框架图 2.系统调用和库函数的区别 3.系统调用的执行过程 参考:<2021王道操作系统考研 ...

  6. 分析teamTNT团队Linux挖矿木马执行过程与防范

    分析teamTNT团队Linux挖矿木马执行过程与防范 公司需要扩展海外业务,需要有一台海外云服务器.当我们把应用部署上去时的第二天所有应用down掉了,然后发现ssh连接服务器特别慢.好不容易连接上 ...

  7. Linux 命令的执行过程/Shell提示符/alias命令

    在 Linux 系统中"一切皆文件",Linux 命令也不例外.那么,当编辑完成 Linux 命令并回车后,系统底层是怎么执行的? 1) 内核层 内核层是 UNIX/Linux 系 ...

  8. linux内核make执行过程

    本篇基于上一篇<<linux内核make menuconfig执行过程>>基础上,追溯make执行过程. make 1. 与make menuconfig相同的部分 这部分内容 ...

  9. Linux系统调用的运行过程【转】

    本文转自:http://blog.csdn.net/kernel_learner/article/details/7331505 在Linux中,系统调用是用户空间访问内核的唯一手段,它们是内核唯一的 ...

最新文章

  1. 杂谈 | 当前知识蒸馏与迁移学习有哪些可用的开源工具?
  2. 解决页面换行因标点符号不能出现在每一行的开头,导致提前换行,中间出现空隙的问题
  3. 【CV】使用 OpenCV 进行图像中的性别预测和年龄检测
  4. Linux字符编码转换 UTF8转GB3212
  5. 线上lnmp环境快速安装
  6. 工业机器人操作机设计原则和设计方法
  7. 拼音模糊搜索 php,基于 XunSearch(迅搜)SDK 的全文搜索 Laravel 5.* 软件包,支持全拼、拼音简写、模糊搜索、热门搜索、搜索提示...
  8. oracle常用函数详解(详细)
  9. 复变函数总结一:复变函数
  10. 标准差(standard deviation)
  11. CSDN表格换行方法
  12. latex 标题chapter section里的英文和数字不加粗
  13. 十年一觉程设梦[完整版]
  14. 哈工大2022计算机系统大作业
  15. 集成电路CAD课程实验报告:反相器电路设计、版图设计与仿真
  16. 网易云热歌榜歌名与热评的高频词抓取及词云制作
  17. java 解析der文件_java-如何读取也用bouncycastle在DER中编码的PK...
  18. 【大会信息分享】新一代推荐算法核心技术与实践
  19. 自主创新让企业有了核心竞争力
  20. Centos 6安装Maven

热门文章

  1. 编译原理(龙书):第一章部分题目参考答案
  2. Spring注解@Primary的意思
  3. 【源码】基于Simulink的混合动力汽车模型
  4. ICCV2021 | DepthInSpace:多帧影像信息在单目结构光深度估计中的应用
  5. (转)Doug Cutting 访谈录 -- 关于搜索引擎的开发
  6. 网易我的世界服务器物品列表,网易我的世界所有物品的英文 | 手游网游页游攻略大全...
  7. PHP获取日历值,分享3个php获取日历的函数
  8. 移动硬盘丢了怎么找回来呢?
  9. Win7使用xp中的超级终端
  10. 无功补偿仿真,simulink无功补偿仿真,matlab无功补偿SVG仿真