- 2025-01-31
-
发表了主题帖:
《Linux内核深度解析》-系统调用学习
一、系统调用
系统调用是内核给用户程序提供的编程接口。用户程序可使用glibc库对单个系统提供的函数,或使用syscall ( ):。系统调用fork()为例:
SYSCALL_DEFINE0(fork) //展开后为 asmlinkage long sys_fork(void)
{
#ifdef CONFIG_MMU
return _do_fork(SIGCHLD,0,0,NULL,NULL,0)
#else
Return -EINVAL;
#endif
}
需要在系统调用表中保存系统调用号和处理函数的映射关系,sys_call_table如下:
/source/arch/x86/kernel/syscall_64.c
asmlinkage const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] =
{
/*
* Smells like a compiler bug -- it doesn't work
* when the & below is removed.*/
[0 ... __NR_syscall_max] = &sys_ni_syscall,
#include <asm/syscalls_64.h>
};
二、执行系统调用
系统调用划分到同步异常,在异常级别1的异常向量表中,64位调用入口为el0_sync函数。
el0_sync:
kernel_entry 0
mrs x25, esr_el1 // 读异常情况寄存器
lsr x24, x25, #ESR_ELx_EC_SHIFT // 判断异常类别
cmp x24, #ESR_ELx_EC_SVC64 // 64-bit 系统调用
b.eq el0_svc //跳转到el0_svc
...
el0_svc负责执行系统调用,如果上层调用open系统调用打开文件时,就会从从sys_call_table,根据系统调用号,找到对应的sys_call_table元素,也即sys_open;并执行。代码如下:
sc_nr .reg x25 //系统调用数量
scno .req x26 //系统调用号
stbl .req x27 //系统调用表地址
Tsk .req x28 //当前进程的thread_info结构体的地址
el0_svc:
adrp stbl, sys_call_table // load syscall table pointer
uxtw scno, w8 // syscall number in w8
mov sc_nr, #__NR_syscalls //把寄存器x25设置为系统调用数量
el0_svc_naked: // compat entry point
stp x0, scno, [sp, #S_ORIG_X0] // save the original x0 and syscall number
enable_dbg //开启调试
enable_irq //开启中断
get_thread_info tsk
ldr x16, [tsk, #TI_FLAGS] // check for syscall tracing
tbnz x16, #TIF_SYSCALL_TRACE, __sys_trace // are we tracing syscalls?
adr lr, ret_fast_syscall // return address,用于返回用户空间
cmp scno, sc_nr // check upper syscall limit
b.hs ni_sys //如果超过系统调用限制,跳转到ni_sys 处理
ldr x16, [stbl, scno, lsl #3] // address in the syscall table
br x16 // call sys_* routine
ni_sys: //超过调用数量限制的处理
mov x0, sp
b do_ni_syscall
ENDPROC(el0_svc)
ret_fast_syscall从系统调用返回用户空间,代码如下
/*
* This is the fast syscall return path. We do as little as possible here,
* and this includes saving x0 back into the kernel stack.
*/
ret_fast_syscall:
disable_irq // disable interrupts
str x0, [sp, #S_X0] // returned x0
ldr x1, [tsk, #TSK_TI_FLAGS] // re-check for syscall tracing
and x2, x1, #_TIF_SYSCALL_WORK
cbnz x2, ret_fast_syscall_trace
and x2, x1, #_TIF_WORK_MASK
cbnz x2, work_pending
enable_step_tsk x1, x2
kernel_exit 0
ret_fast_syscall_trace:
enable_irq // enable interrupts
b __sys_trace_return_skipped // we already saved x0
/*
* Ok, we need to do extra processing, enter the slow path.
*/
work_pending:
mov x0, sp // 'regs'
bl do_notify_resume
#ifdef CONFIG_TRACE_IRQFLAGS
bl trace_hardirqs_on // enabled while in userspace
#endif
ldr x1, [tsk, #TSK_TI_FLAGS] // re-check for single-step
b finish_ret_to_user
/*
* "slow" syscall return path.
*/
ret_to_user:
disable_irq // disable interrupts
ldr x1, [tsk, #TSK_TI_FLAGS]
and x2, x1, #_TIF_WORK_MASK
cbnz x2, work_pending
finish_ret_to_user:
enable_step_tsk x1, x2
kernel_exit 0
ENDPROC(ret_to_user)
work_pending调用do_notify_resume函数,代码如下:
asmlinkage void do_notify_resume(struct pt_regs *regs,
unsigned int thread_flags)
{
/*
* The assembly code enters us with IRQs off, but it hasn't
* informed the tracing code of that for efficiency reasons.
* Update the trace code with the current status.
*/
trace_hardirqs_off();
do {
if (thread_flags & _TIF_NEED_RESCHED) {
schedule();
} else {
local_irq_enable();
if (thread_flags & _TIF_UPROBE)
uprobe_notify_resume(regs);
if (thread_flags & _TIF_SIGPENDING)
do_signal(regs);
if (thread_flags & _TIF_NOTIFY_RESUME) {
clear_thread_flag(TIF_NOTIFY_RESUME);
tracehook_notify_resume(regs);
}
if (thread_flags & _TIF_FOREIGN_FPSTATE)
fpsimd_restore_current_state();
}
local_irq_disable();
thread_flags = READ_ONCE(current_thread_info()->flags);
} while (thread_flags & _TIF_WORK_MASK);
}
-
发表了日志:
《Linux内核深度解析》-系统调用学习
- 2025-01-17
-
回复了主题帖:
《Linux内核深度解析》-异常处理
oxlm_1 发表于 2025-1-17 09:23
一个模块一个模块地啃还是可以搞懂,只是linux内核太大了,内容太多了,没书完全不知道从哪开始啃
操作系统要坚持看,睡觉前比起床前进步一点点,极少成多,慢慢就有感觉了。这种知识不是一会半会的功夫。
- 2025-01-16
-
回复了主题帖:
《Linux内核深度解析》-异常处理
T_T1111 发表于 2025-1-16 20:29
表示没有看懂,看着不像ARM或者X86
我写的不全,另外Linux内核确实比较难。
- 2025-01-14
-
回复了主题帖:
《Linux内核深度解析》-异常处理
风尘流沙 发表于 2025-1-14 14:35
已下载学习了,谢谢楼主的介绍。
共同进步
-
发表了主题帖:
《Linux内核深度解析》-异常处理
《Linux内核深度解析》-异常处理
一、异常级别
二、异常分类
异常分为同步异常和异步异常。如下图所示
三、异常处理
以出现用户模式(异常级别0)下访问数据时生成的页错误异常为例。
el0_sync:
kernel_entry 0
mrs x25, esr_el1 // read the syndrome register
lsr x24, x25, #ESR_EL1_EC_SHIFT // exception class
cmp x24, #ESR_EL1_EC_SVC64 // SVC in 64-bit state
b.eq el0_svc
cmp x24, #ESR_EL1_EC_DABT_EL0 // data abort in EL0
b.eq el0_da //调用el0_da
cmp x24, #ESR_EL1_EC_IABT_EL0 // instruction abort in EL0
b.eq el0_ia
cmp x24, #ESR_EL1_EC_FP_ASIMD // FP/ASIMD access
b.eq el0_fpsimd_acc
cmp x24, #ESR_EL1_EC_FP_EXC64 // FP/ASIMD exception
b.eq el0_fpsimd_exc
cmp x24, #ESR_EL1_EC_SYS64 // configurable trap
b.eq el0_undef
cmp x24, #ESR_EL1_EC_SP_ALIGN // stack alignment exception
b.eq el0_sp_pc
cmp x24, #ESR_EL1_EC_PC_ALIGN // pc alignment exception
b.eq el0_sp_pc
cmp x24, #ESR_EL1_EC_UNKNOWN // unknown exception in EL0
b.eq el0_undef
cmp x24, #ESR_EL1_EC_BREAKPT_EL0 // debug exception in EL0
b.ge el0_dbg
b el0_inv
对于用户模式(异常级别0)下生成的同步异常,入口是el0_sync。该入口函数在arch/arm64/kernel/entry.s中
通过上面代码,跳到对应的异常处理函数,
其中:访问数据时生成的页错误异常,通过( b.eq el0_da //调用el0_da )这行代码,进入 el0_da 函数。
下面为 el0_da 函数代码
el0_da:
/*
* Data abort handling
*/
mrs x26, far_el1 //获取数据的虚拟地址
// enable interrupts before calling the main handler
enable_dbg_and_irq //开启调试异常和中断
ct_user_exit
bic x0, x26, #(0xff << 56)
mov x1, x25
mov x2, sp
bl do_mem_abort //调用do_mem_abort函数
b ret_to_user
通过do_mem_abort( ) 函数将异常信息进行处理。
- 2025-01-13
-
回复了主题帖:
《Linux内核深度解析》-内存管理
hjh0512 发表于 2025-1-13 19:13
这个看的容易理解,有没有完整版的,哪里能找到
《Linux内核深度解析》这本书上的,我稍微捡着一些总结了一下,你可以找找网上有没有电子版,或者买一本看一下。
-
发表了主题帖:
《Linux内核深度解析》-内存管理
《Linux内核深度解析》-内存管理
一、内存管理架构
内存分为:用户空间、内核空间、硬件3个层面
二、内核地址空间(ARM64)
布局如下图;
三、内存管理应用系统调用接口
mmap()创建内存映射
mremap()扩大或缩小已存在的内存映射
munmap()删除内存映射
brk()设置堆上限
mprotect()设置虚拟内存的访问权限
- 2025-01-03
-
发表了主题帖:
《Linux内核深度解析》之进程的创建
进程的创新主要有fork、clone等的系统调用来完成。通过函数_do_fork实现。
_do_fork主要完成以下工作:
1、调用copy_process函数
2、调用wake_up_new_task函数唤醒新进程
/*
* Ok, this is the main fork-routine.
*
* It copies the process, and if successful kick-starts
* it and waits for it to finish using the VM if required.
*/
long do_fork(unsigned long clone_flags,
unsigned long stack_start,
struct pt_regs *regs,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr)
{
struct task_struct *p;
int trace = 0;
long pid;
if (unlikely(current->ptrace)) {
trace = fork_traceflag (clone_flags);
if (trace)
clone_flags |= CLONE_PTRACE;
}
p = copy_process(clone_flags, stack_start, regs, stack_size, parent_tidptr, child_tidptr);
/*
* Do this prior waking up the new thread - the thread pointer
* might get invalid after that point, if the thread exits quickly.
*/
pid = IS_ERR(p) ? PTR_ERR(p) : p->pid;
if (!IS_ERR(p)) {
struct completion vfork;
if (clone_flags & CLONE_VFORK) {
p->vfork_done = &vfork;
init_completion(&vfork);
}
if ((p->ptrace & PT_PTRACED) || (clone_flags & CLONE_STOPPED)) {
/*
* We'll start up with an immediate SIGSTOP.
*/
sigaddset(&p->pending.signal, SIGSTOP);
set_tsk_thread_flag(p, TIF_SIGPENDING);
}
p->state = TASK_STOPPED;
if (!(clone_flags & CLONE_STOPPED))
wake_up_forked_process(p); /* do this last */
++total_forks;
if (unlikely (trace)) {
current->ptrace_message = pid;
ptrace_notify ((trace << 8) | SIGTRAP);
}
if (clone_flags & CLONE_VFORK) {
wait_for_completion(&vfork);
if (unlikely (current->ptrace & PT_TRACE_VFORK_DONE))
ptrace_notify ((PTRACE_EVENT_VFORK_DONE << 8) | SIGTRAP);
} else
/*
* Let the child process run first, to avoid most of the
* COW overhead when the child exec()s afterwards.
*/
set_need_resched();
}
return pid;
}
copy_process函数的主要工作:
通过dup_task_struct函数为进程分配内存空间。
通过sched_fork函数为进程设置调度器
通过copy_semundo、copy_files、copy_fs、copy_sighand、copy_signal、copy_mm、copy_namespaces、copy_io、copy_thread_tls函数复制共享资源。
wake_up_new_task函数的主要工作:
将新进程的状态切换到TASK_RUNNING
将新进程加入到运行队列
- 2024-12-25
-
回复了主题帖:
《Linux内核深度解析》之环境准备与第一章学习
qzgiky 发表于 2024-12-24 11:28
学习手搓一个操作系统
一开始想着自已做一个的,不过这个系统有点太庞大了。现在感觉如果想自己编,应该从基础学起,比如说:有一定的RTOS的基础,然后如果做的话,先考虑低版本的系统开始做。
- 2024-12-24
-
发表了主题帖:
《Linux内核深度解析》之环境准备与第一章学习
《Linux内核深度解析》第一章学习
一、准备工作
首先在Index of /pub/linux/kernel/v4.x/网站下载对应的4.12版本。
通过sourceinsight进行代码查看.
没有发现start.S文件,怀疑自己的解压方式有问题,采用lubuntu的share文件夹进行解压,依然没有发现对应文件。
学习代码结构
start.S的作用
Start.S左右启动的开始主要有以下作用:
通过调用各种板卡函数save_bool_params来保存重要的寄存器。
调用函数reset_sctrl来初始化系统控制寄存器。
根据处理器异常情况设置寄存器。
为处理器的缺陷打补丁。
简单设置主从处理器
跳到main函数
main函数的作用
为调用函数board_init_f做准备。调用board_init_f函数
把 U-Boot 程序复制到内存中,之后处理器从内存中取数据。
重新定位栈
调用函数 board_init_r,以此执行 init_sequence_r中的每个函数,直到run_main_loop函数。
- 2024-12-11
-
回复了主题帖:
读书入围名单: 《Linux内核深度解析》
个人信息无误,确认可以完成阅读分享计划