引用我演讲中的话:
注意到用户空间和内核空间之间的清晰边界。用户程序不能在代码中包含内核头,也不能直接调用内核函数。换句话说,您的程序不能简单地调用sys_read()服务函数从磁盘读取文件。类似地,内核代码不像printf()那样调用用户空间函数,不包括像这样的用户空间标头,也不链接到libc这样的用户空间库。如前所述,用户可以使用的内核模式(和OS服务)的唯一入口是syscall指令。
getpid()时,这个用户空间函数是吗?

我打开了一些linux文件(init/main.c)并看到:
static int run_init_process(const char *init_filename)
{
argv_init[0] = init_filename;
return do_execve(getname_kernel(init_filename),....
}这个do_execve在哪里声明的?图中只显示execv和sys_execv..。有什么区别吗?
发布于 2021-05-07 19:38:44
“用户程序不能包括内核标题”,所以当我在C程序getpid()中编写时,这个用户空间函数是吗?
是。它是libc中调用系统调用的薄包装器。但是,取决于体系结构和libc实现,在用户空间中可能会有一些簿记(例如,为以后的调用缓存结果)。
对于许多简单的系统,glibc使用预处理宏生成这些包装器。在我的系统中,对getpid的用户空间调用转到sysdeps/unix/syscall-template.S文件。
0x00007ffff7ea6244 59 in ../sysdeps/unix/syscall-template.S
(gdb) disassemble
Dump of assembler code for function getpid:
0x00007ffff7ea6240 <+0>: endbr64
=> 0x00007ffff7ea6244 <+4>: mov $0x27,%eax
0x00007ffff7ea6249 <+9>: syscall
0x00007ffff7ea624b <+11>: retq
End of assembler dump.它简单地将syscall号放在寄存器中并执行syscall指令。
我们使用这个包装器的原因是为了避免了解不同体系结构和内核的syscall机制和数量的具体情况。这使得我们的程序更便携。我们所链接的libc知道0x27是这个系统上的getpid,并且应该写入%eax等等。
执行syscall指令时,处理器切换到内核模式,并从arch/x86/entry/entry_64.S开始执行,其中entry_SYSCALL_64调用位于arch/x86/entry/common.c中的do_syscall_64。
regs->ax = sys_call_table[nr](regs);您可以看到,它在索引nr处调用sys_call_table的函数。此表由符号列表(sys_something)填充,其中每个符号由宏:SYSCALL_DEFINEn定义,其中n是参数数。因为getpid不是一个参数,所以在kernel/sys.c中它被定义为SYSCALL_DEFINE0(getpid)
/**
* sys_getpid - return the thread group id of the current process
*
* Note, despite the name, this returns the tgid not the pid. The tgid and
* the pid are identical unless CLONE_THREAD was specified on clone() in
* which case the tgid is the same in all threads of the same group.
*
* This is SMP safe as current->tgid does not change.
*/
SYSCALL_DEFINE0(getpid)
{
return task_tgid_vnr(current);
},当我在终端中输入getpid时,它是相同的(使用空间函数)吗?
我不知道终端命令getpid,但如果有,它将是一个可执行的二进制文件(或脚本),最终会调用syscall或syscall的libc包装器。因为内核维护任务和进程ID,并且用户空间代码无法访问内核内存。
我不能访问我的系统/home/ user / linux -4.15中的linux头文件,那么怎么说用户空间不能访问内核空间呢?
你是说你可以访问头文件吗?当然,您可以访问整个源代码。但是,即使您在程序中包含了这些头部,并编译它们,并以某种方式将它们与内核代码链接起来,但这并不意味着您可以在内核模式下运行它们。
除了,如果您使用可加载的内核模块。实际上,您需要内核头文件来编译内核模块。然后,您可以请求内核以内核模式加载和执行这些模块。但是您需要调用另一个syscall (init_module)来实现这一点。
,do_execve在哪里声明的?图中只显示了execv和sys_execv..。有什么区别吗?
以下是syscall execve的定义
SYSCALL_DEFINE3(execve,
const char __user *, filename,
const char __user *const __user *, argv,
const char __user *const __user *, envp)
{
return do_execve(getname(filename), argv, envp);
}与getpid类似,execve是用一个生成sys_execve符号的SYSCALL_DEFINEn (这次是三个参数)宏定义的。在内部,内核调用do_execve。如果搜索文件的其余部分,您将看到do_execve本身是do_execveat_common的包装器。经过一些检查和初始化后,调用bprm_execve,它调用exec_binprm,等等。
,你能详细说明一下
do_execve和sys_execve的区别吗?
没什么区别。除了,sys_execve符号是由SYSCALL_DEFINE3宏定义的,它是由特定于体系结构的syscall机制调用的,它可以不同于常规的C函数(例如asmlinkage)。do_execve是一个正则C函数。在这个例子中,它不是从任何其他C代码中调用的,但是它是可能的。但是,直接从内核代码中调用sys_execve是不正确的。
https://stackoverflow.com/questions/67439775
复制相似问题