结合中断上下文切换和进程上下文切换分析Linux内核的一般执行过程
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了结合中断上下文切换和进程上下文切换分析Linux内核的一般执行过程,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含8566字,纯文字阅读大概需要13分钟。
内容图文
![结合中断上下文切换和进程上下文切换分析Linux内核的一般执行过程](/upload/InfoBanner/zyjiaocheng/938/7c7a4375b76e48baae7893fcdc76aa21.jpg)
结合中断上下文切换和进程上下文切换分析Linux内核一般执行过程
- 以fork和execve系统调用为例分析中断上下文的切换
- 分析execve系统调用中断上下文的特殊之处
- 分析fork子进程启动执行时进程上下文的特殊之处
- 以系统调用作为特殊的中断,结合中断上下文切换和进程上下文切换分析Linux系统的一般执行过程
CPU上下文切换可以分为以下三种
1.进程上下文切换
是指从一个进程切换到另一个进程运行。一个进程既可以在用户空间运行,也可以在内核空间运行,因此进程的上下文不仅包括了虚拟内存、栈、全局变量等用户空间的资源,还包括了内核堆栈、寄存器等内核空间的状态。进程上下文切换需要保存上述资源,加载新进程(内核态)并装入新进程的上下文
2.线程上下文切换
线程是调度的基本单位,进程是资源拥有的基本单位 通俗理解:系统内核中的任务调度实际上调度的对象是线程,而进程只是给线程提供了虚拟内存,全局变量等资源这些资源在线程上下文切换时是不需要修改的
3.中断上下文切换
中断优先级会打断进程的正常调度和执行
对于同一个CPU来说,中断处理比进程拥有更高的优先级
硬件/软件通过中断触发信号,导致内核调用中断处理程序,进入内核空间。这个过程中,硬件的一些变量和参数也要传递给内核,内核通过这些参数进行中断处理。中断上下文可以看作就是硬件传递过来的这些参数和内核需要保存的一些其他环境。因此中断上下文的切换是一直发生在内核态的,相比进程上下文切换要轻量很多。
中断上下?和进程上下?的?个关键区别是堆栈切换的?法。中断是由CPU实 现的,所以中断上下?切换过程中最关键的栈顶寄存器sp和指令指针寄存器ip 是由CPU协助完成的;进程切换是由内核实现的,所以进程上下?切换过程中 最关键的栈顶寄存器sp切换是通过进程描述符的thread.sp实现的,指令指针 寄存器ip的切换是在内核堆栈切换的基础上巧妙利?call/ret指令实现的。
分析fork子进程启动执行时进程上下文的特殊之处
fork系统调用用于创建一个新进程,称为子进程,它与进程(称为系统调用fork的进程)同时运行,此进程称为父进程。创建新的子进程后,两个进程将执行fork()系统调用之后的下一条指令。子进程使用相同的pc(程序计数器),相同的CPU寄存器,在父进程中使用的相同打开文件。它不需要参数并返回一个整数值。下面是fork()返回的不同值:
-1:创建子进程失败。
0:返回到新创建的子进程。
>0:返回父进程pid。
#include <unistd.h> #include <stdio.h> #include <sys/types.h> int main (){ pid_t pid; pid = fork(); if (pid < 0){ perror("fork error:\n"); } else if (pid == 0){ printf("i am child, my pid is %d\n",getpid()); } else{ printf("i am parent, my pid is %d\n",getpid()); } return 0; }
fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
1)在父进程中,fork返回新创建子进程的进程ID;
2)在子进程中,fork返回0;
3)如果出现错误,fork返回一个负值;
在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。进程形成了链表,父进程的fpid(p 意味point)指向子进程的进程id,因为子进程没有子进程,所以其fpid为0.
查看do_fork的源码:
long do_fork(unsigned long clone_flags, unsigned long stack_start, unsigned long stack_size, int __user *parent_tidptr, int __user *child_tidptr) { struct task_struct *p; int trace = 0; long nr; /* * Determine whether and which event to report to ptracer. When * called from kernel_thread or CLONE_UNTRACED is explicitly * requested, no event is reported; otherwise, report if the event * for the type of forking is enabled. */ if (!(clone_flags & CLONE_UNTRACED)) { if (clone_flags & CLONE_VFORK) trace = PTRACE_EVENT_VFORK; else if ((clone_flags & CSIGNAL) != SIGCHLD) trace = PTRACE_EVENT_CLONE; else trace = PTRACE_EVENT_FORK; if (likely(!ptrace_event_enabled(current, trace))) trace = 0; } p = copy_process(clone_flags, stack_start, stack_size, child_tidptr, NULL, trace); /* * Do this prior waking up the new thread - the thread pointer * might get invalid after that point, if the thread exits quickly. */ if (!IS_ERR(p)) { struct completion vfork; struct pid *pid; trace_sched_process_fork(current, p); pid = get_task_pid(p, PIDTYPE_PID); nr = pid_vnr(pid); if (clone_flags & CLONE_PARENT_SETTID) put_user(nr, parent_tidptr); if (clone_flags & CLONE_VFORK) { p->vfork_done = &vfork; init_completion(&vfork); get_task_struct(p); } wake_up_new_task(p); /* forking complete and child started to run, tell ptracer */ if (unlikely(trace)) ptrace_event_pid(trace, pid); if (clone_flags & CLONE_VFORK) { if (!wait_for_vfork_done(p, &vfork)) ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid); } put_pid(pid); } else { nr = PTR_ERR(p); } return nr; }
可以看到,第29行copy_process(clone_flags, stack_start, stack_size, 30 child_tidptr, NULL, trace);函数生成了新的进程。
copy_process所做的工作如下:
- 定义返回值亦是retval和新的进程描述符task_struct结构p。
- 标志合法性检查。对clone_flags所传递的标志组合进行合法性检查。
- 安全性检查。通过调用security_task_create()和后面的security_task_alloc()执行所有附加的安全性检查。询问 Linux Security Module (LSM) 看当前任务是否可以创建一个新任务。LSM是SELinux的核心。
- 复制进程描述符。通过dup_task_struct()为子进程分配一个内核栈、thread_info结构和task_struct结构。
- 一些初始化。通过诸如ftrace_graph_init_task,rt_mutex_init_task完成某些数据结构的初始化。调用copy_creds()复制证书(复制权限及身份信息)。
- 检测系统中进程的总数量是否超过了max_threads所规定的进程最大数。
- 复制标志。通过copy_flags,将从do_fork()传递来的的clone_flags和pid分别赋值给子进程描述符中的对应字段。
- 初始化子进程描述符。初始化其中的各个字段,使得子进程和父进程逐渐区别出来。这部分工作包含初始化子进程中的children和sibling等队列头、初始化自旋锁和信号处理、初始化进程统计信息、初始化POSIX时钟、初始化调度相关的统计信息、初始化审计信息。
- 调度器设置。调用sched_fork函数执行调度器相关的设置,为这个新进程分配CPU,使得子进程的进程状态为TASK_RUNNING。并禁止内核抢占。并且,为了不对其他进程的调度产生影响,此时子进程共享父进程的时间片。
- 复制进程的所有信息。根据clone_flags的具体取值来为子进程拷贝或共享父进程的某些数据结构。比如copy_semundo()、复制开放文件描述符(copy_files)、复制符号信息(copy_sighand 和 copy_signal)、复制进程内存(copy_mm)以及最终复制线程(copy_thread)。
- 复制线程。通过copy_threads()函数更新子进程的内核栈和寄存器中的值。在之前的dup_task_struct()中只是为子进程创建一个内核栈,至此才是真正的赋予它有意义的值。
- 分配pid。用alloc_pid函数为这个新进程分配一个pid,Linux系统内的pid是循环使用的,采用位图方式来管理。简单的说,就是用每一位(bit)来标示该位所对应的pid是否被使用。分配完毕后,判断pid是否分配成功。成功则赋给p->pid。
- 更新属性和进程数量。根据clone_flags的值继续更新子进程的某些属性。将 nr_threads加一,表明新进程已经被加入到进程集合中。将total_forks加一,以记录被创建进程数量。
- 如果上述过程中某一步出现了错误,则通过goto语句跳到相应的错误代码处;如果成功执行完毕,则返回子进程的描述符p。
copy_process()执行完后返回do_fork(),do_fork()执行完毕后,虽然子进程处于可运行状态,但是它并没有立刻运行。至于子进程何时执行则取决于调度程序。
Linux系统的一般执行过程
中断和中断返回有中断上下?的切换,CPU和内核代码中断处理程序??的汇编代码结合起来完成中断上下?的切换。进程调度过程中有进程上下?的切换,?进程上下?的切换完全由内核完成,具体包括:从?个进程的地址空间切换到另?个进程的地址空间;从?个进程的内核堆栈切换到另?个进程的内核堆栈;还有进程的CPU上下?的切换。
中断上下?和进程上下?的?个关键区别是堆栈切换的?法。中断是由CPU实现的,所以中断上下?切换过程中最关键的栈顶寄存器sp和指令指针寄存器ip是由CPU协助完成的;进程切换是由内核实现的,所以进程上下?切换过程中最关键的栈顶寄存器sp切换是通过进程描述符的thread.sp实现的,指令指针寄存器ip的切换是在内核堆栈切换的基础上巧妙利?call/ret指令实现的。一个完整的linux系统运行过程,可描述如下:
1 正在运?的?户态进程X发?中断时(包括异常、系统调?等),CPU完成load cs:rip(entry of a specifific ISR),即跳转到中断处理程序??。然后进入到中断上下文切换的步骤:
[1] swapgs指令保存现场,可以理解CPU通过swapgs指令给当前CPU寄存器状态做了?个快照。
[2] rsp point to kernel stack,加载当前进程内核堆栈栈顶地址到RSP寄存器。快速系统调?是由系统调???处的汇编代码实现?户堆栈和内核堆栈的切换。
[3] save cs:rip/ss:rsp/rflflags:将当前CPU关键上下?压?进程X的内核堆栈,快速系统调?是由系统调???处的汇编代码实现的。
至此,完成了中断上下?切换,即从进程X的?户态到进程X的内核态。
2 中断处理过程中或中断返回前调?了schedule函数,其中完成了进程调度算法选择next进程、进程地址空间切换、以及switch_to关键的进程上下?切换等。
3 switch_to调?了__switch_to_asm汇编代码做了关键的进程上下?切换。将当前进程X的内核堆栈切换到进程调度算法选出来的next进程(假定为进程Y)的内核堆栈,并完成了进程上下?所需的指令指针寄存器状态切换。之后开始运?进程Y。
4 中断上下?恢复,与1中断上下?切换相对应。注意这?是进程Y的中断处理过程中,?1中断上下?切换是在进程X的中断处理过程中,因为从用户态切换到内核态,会导致内核堆栈从进程X切换到进程Y了。
5 为了对应起?中断上下?恢复的最后?步,单独拿出来iret - pop cs:rip/ss:rsp/rflflags,从Y进程的内核堆栈中弹出1中对应的压栈内容。此时完成了中断上下?的切换,即从进程Y的内核态返回到进程Y的?户态。这里因sysret和iret的不同而略有差异。
6 继续运??户态进程Y。
内容总结
以上是互联网集市为您收集整理的结合中断上下文切换和进程上下文切换分析Linux内核的一般执行过程全部内容,希望文章能够帮你解决结合中断上下文切换和进程上下文切换分析Linux内核的一般执行过程所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。