内核堆栈:每个进程在内核模式下运行时都有自己的内核堆栈。这个堆栈保存了进程在内核模式下的运行状态,包括函数调用时传递的参数、局部变量和返回地址等。
用户态与内核态:进程通常在用户态下运行,当执行系统调用或响应中断时进入内核态。在内核态,进程对硬件资源有更高级别的控制。
返回地址:当系统调用或中断发生时,CPU从用户态切换到内核态并保存返回用户态的地址。当内核完成处理后,通过这个地址返回用户态继续执行原进程。
进程切换的实质操作发生在内核堆栈上。当操作系统决定将CPU从当前进程切换到另一个进程时,它会保存当前进程的内核堆栈信息(即执行“保存上下文”),然后加载新的进程的内核堆栈信息(即执行“恢复上下文”)到CPU。
关键步骤是修改内核堆栈上的返回地址:
- 对于当前正在运行的进程,其在内核堆栈上的信息会被保存,包括即将返回到用户态执行的地址。
- 当要切换到新的进程时,操作系统会更改内核堆栈上的数据为新进程的堆栈信息,并设置新的返回地址为新进程的用户态地址。
当操作系统做好这些更改后,它将“返回”到新进程的用户态,从新进程保存的那个点继续执行。从用户的角度看,CPU似乎从一个进程“跳”到了另一个。实际上,这个“跳”是通过保存和恢复不同进程的上下文、修改内核堆栈上的数据完成的。这样,一个进程切换到另一个进程的过程就完成了,CPU现在执行新的进程代码。
内核态:
在内核态可以理解为不分进程,进入内核态就不分了。只在内核态返回到用户进程才有意义。
进程上下文: 在用户态,操作系统为每个运行的程序提供了一个独立的进程上下文,包括虚拟内存空间、文件句柄、安全权限等。每个进程都拥有自己独立的用户态环境,操作系统通过进程间切换(Context Switch)来管理多任务。
内核态简化: 在内核态,虽然当前可能是响应某个进程的系统调用进入的,但是由于所有进程共享相同的内核空间和内核资源,所以在一些操作中不需要考虑是哪个用户进程在请求服务。
共享内核资源: 内核态代码直接运行在处理器的最高权限级别,访问的是全局的、不属于特定进程的资源。比如内核的调度器、内存管理单元、I/O处理系统等,都是服务于所有进程的共享资源。
安全隔离与共享: 即使在内核态有时也需要知道当前操作是由哪个进程触发的(例如管理进程的内核栈、确保访问权限等),但许多低级别的内核操作本身是与发起请求的特定进程无关的,比如硬件中断处理、定时器调度、底层网络数据包处理等。
linux内核如何加载多种不同的可执行文件?
- shell 环境下调用execve同时载入命令行参数和环境变量
- 库函数触发系统调用陷入内核态
- system call 调用sys_execuve -> do_execve -> do_execve_common -> exec_binprm-> load_elf_binary
在系统初始化阶段调用了core_initcall(init_elf_binfmt);
init_elf_binfmt 中将 elf_format全局变量注册在内核的fmt链表中,所以在 exec_binprm 中会在fmt链表中找到对应模块来解析ELF文件的头部信息。
调用 elf_format变量中的 load_elf_binary 方法,load_elf_binary 中最后调用start_thread 设置新的ip值,等该进程返回用户态时,就转而执行新的代码。
两种加载的方法:
静态库:直接执行可执行程序的入口
动态库:由ld来动态链接这个程序,再把控制权移交给可执行程序的入口