操作系统概览
操作系统
- 内核
- 进程管理、进程调度、进程间通讯机制,内存管理,中断异常处理,文件系统,I/O系统
- 其他程序
- 函数库,shell程序
操作系统的目的
-
与硬件交互,管理所有的硬件资源
-
为用户程序提供一个执行环境
linux一般执行过程
最一般的情况 :
正在运行的用户态进程x切换到运行用户态进行y
- x正在运行
- 发生中断-cpu将当前的进程上下文压入到进程x的内核堆栈
- SAVE_ALL 保存现场
- 中断处理过程或中断返回之前调用schdule(),其中的switch_to()做了关键的进程上下文切换
- 标号1之后开始运行进程y
- restore_all 恢复现场
- iret
- 继续运行进程y
特殊情况
- 通过中断处理过程中的调度时机,用户态进程与内核线程之间互相切换,与最一般的情况类似,只是内核线程运行过程中发生中断没有用户态和内核态的转换
- (用户态不能直接调用schdule) 内核线程主动调用schdule(),只有进程上下文的切换,没有发生中断上下文的切换
- 创建子进程的系统调用在子进程的执行起点返回用户态,如fork
- 加载一个新的可执行程序后返回到用户态的情况如execve
进程调度
- I/O
- 频繁的进行I/O
- 通常会花费很多时间等待进程完成
- cpu
- 计算密集形
- 需要大量的cpu时间进行计算
- 批处理进程
- 不必很快和用户交互,通常在后台运行
- 不必很快响应
- 典型的批处理进程:编译程序、科学计算
- 实时进程
- 有实时需求,优先级高的先行
- 响应时间短,要稳定
- 如音频,机械控制等
- 交互式进程
- 频繁与用户交互,需要等待用户输入
- 响应时间要快
- 如shell,文本编辑
基于以上进程的区别,所以需要进程调度
schdule函数负责实现调度
一般的用户态进程,只能被动调度
内核线程是只有内核态没有用户态的特殊进程
进程切换代码
当前进程怎么切换到next进程
为了控制进程的执行,内核必须有能力挂起cpu正在执行的进程,并且能够恢复之前被挂起的进程,这与中断上下文切换不同(中断在同一个进程中)
进程上下文切换:
- 用户地址空间
- 控制信息
- 硬件上下文
next = pick_next_task(rq, prev); //包装使用某种进程调度策略
context_switch
关键代码
context_switch(struct rq *rq, struct task_struct *prev,
struct task_struct *next)
{
struct mm_struct *mm, *oldmm;
prepare_task_switch(rq, prev, next);
mm = next->mm;
oldmm = prev->active_mm;
/*
* For paravirt, this is coupled with an exit in switch_to to
* combine the page table reload and the switch backend into
* one hypercall.
*/
arch_start_context_switch(prev);
if (!mm) {
next->active_mm = oldmm;
atomic_inc(&oldmm->mm_count);
enter_lazy_tlb(oldmm, next);
} else
switch_mm(oldmm, mm, next);
if (!prev->mm) {
prev->active_mm = NULL;
rq->prev_mm = oldmm;
}
/*
* Since the runqueue lock will be released by the next
* task (which is an invalid locking op but in the case
* of the scheduler it's an obvious special-case), so we
* do an early lockdep release here:
*/
spin_release(&rq->lock.dep_map, 1, _THIS_IP_);
context_tracking_task_switch(prev, next);
/* Here we just switch the register state and the stack. */
switch_to(prev, next, prev);
barrier();
/*
* this_rq must be evaluated again because prev may have moved
* CPUs since it called schedule(), thus the 'rq' on its stack
* frame will be invalid.
*/
finish_task_switch(this_rq(), prev);
}
next_ip
一般是$1f,对于新创建的子进程是ret_from_fork
schedule 调试
和之前的调试其实差不多,只不过这个词把断点设置在schedule
然后单步执行就行