IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> linux内核中断返回调度标志是如何设置的 -> 正文阅读

[系统运维]linux内核中断返回调度标志是如何设置的

系统调用是常见一种类型的异常,也是应用代码从用户空间主动进入内核空间的唯一方式。

运行在用户空间的进程会被动陷入到内核空间,进行中断处理程序的处理。

中断处理程序处理完后返回到用户空间,在返回至用户空间之前,判断是否要进行进程调度。

如果需要则再进行一次进程调度。

中断处理程序

// arch/arm64/kernel/entry.S:838
// arm64 的中断入口
el0_irq:
    ...
    处理中断
    ...
    // 回到用户空间
    b ret_to_user
	
// arch/arm64/kernel/entry.S:895
ret_to_user:
    ...
    ldr	x1, [tsk, #TSK_TI_FLAGS]
    and	x2, x1, #_TIF_WORK_MASK
    cbnz x2, work_pending

el0_irq

被中断暂停的当前进程将?tsk数据结构中,偏移量为?TSK_TI_FLAGS?传递给 x1 寄存器。

TSK_TI_FLAGS?常量在?asm-offsets.c?文件中被定义。

//?arch/arm64/kernel/asm-offsets.c:48
int?main(void)?{
????...
????DEFINE(TSK_TI_FLAGS,?offsetof(struct?task_struct,?thread_info.flags))
????...????????
}

?task_struct?结构中的?thread_info?结构中的?flags?字段的偏移量:

//?include/linux/sched.h:592
struct?task_struct?{
????...
????struct?thread_info?thread_info;
????...
}

//?arch/arm64/include/asm/thread_info.h:39
struct?thread_info?{
????...
????unsigned?long?flags;
????...
}

ret_to_user

取出?task_struct->thread_info->flags?字段,与?_TIF_WORK_MASK?进行 and 操作:

//?arch/arm64/include/asm/thread_info.h:118
#define?_TIF_WORK_MASK??(_TIF_NEED_RESCHED?|?_TIF_SIGPENDING?|?\
?????_TIF_NOTIFY_RESUME?|?_TIF_FOREIGN_FPSTATE?|?\
?????_TIF_UPROBE?|?_TIF_FSCHECK)

flags?与?_TIF_WORK_MASK?进行 and 操作之后。

如二进制位的值不为 0,cbnz跳转到?work_pending?方法。

// arch/arm64/kernel/entry.S:884 
work_pending:
    ...
	bl	do_notify_resume
	...
	
// arch/arm64/kernel/signal.c:915	
// 参数thread_flags就是保存在x1寄存器中的值 即task_struct>thread_info->flags
void do_notify_resume(... long thread_flags) {
    ...
    if (thread_flags & _TIF_NEED_RESCHED) {
        schedule();
    } 
    ...
}

当中断处理程序返回用户空间的时候,如果被中断的进程被设置了需要进程调度标志,那么就进行一次进程调度。

如何被设置调度标志?

只有进入到内核空间才能够设置当前进程的需要调度标志。

而系统调用是主动从用户空间进入内核空间的唯一方式。

有哪些系统调用会设置当前进程调度的标志???

fork创建新进程设置调度标志

通过fork系统调用创建新的进程。

//?kernel/fork.c:2291
SYSCALL_DEFINE0(fork)?{
????...
????return?_do_fork(...);
}

//?kernel/fork.c:2196
long?_do_fork(...)?{
????struct?task_struct?*p;
????...
    //?copy继承父进程资源
????p?=?copy_process(...);
????...
    //?创建子进程后 wakeup子进程
????wake_up_new_task(p);
????...
}

创建完新进程后调用wake_up_new_task唤醒新进程:

//?kernel/sched/core.c:2413
void?wake_up_new_task(struct?task_struct?*p)?{
????...
    //?当前进程设置为RUNNING状态
?   p->state?=?TASK_RUNNING;
????...
    //?是否要抢占当前进程
?   check_preempt_curr(rq,?p,?WF_FORK);
????...
}

check_preempt_curr?会根据当前进程的调度类型,执行对应的方法:

//?kernel/sched/core.c:854
void?check_preempt_curr(struct?rq?*rq,?struct?task_struct?*p,?int?flags)?{
????...
    //?rq当前cpu上的进程队列、curr当前正在cpu运行的进程、sched_class当前进程的调度
????rq->curr->sched_class->check_preempt_curr(rq,?p,?flags);
????...
}

sched_class表示进程的调度类型。

//?include/linux/sched.h:592
struct?task_struct?{
????...
//??sched_class?在进程的数据结构中
//??表示调度类型,我们后面的系列文章再详细分析?
????const?struct?sched_class?*sched_class;
????...
}

//?kernel/sched/sched.h:1715
//?Linux?中所有的调度类型
extern?const?struct?sched_class?stop_sched_class;
extern?const?struct?sched_class?dl_sched_class;
extern?const?struct?sched_class?rt_sched_class;
extern?const?struct?sched_class?fair_sched_class;
extern?const?struct?sched_class?idle_sched_class;

fair_sched_class为一般进程的调度类型(公平调度)。

fair_sched_class?的?check_preempt_check?方法:

//?kernel/sched/fair.c:10506
const?struct?sched_class?fair_sched_class?=?{
    .check_preempt_curr?=?check_preempt_wakeup
}

//?kernel/sched/fair.c:6814
static?void?check_preempt_wakeup(rq?*rq,?task_struct?*p...)?{
????struct?task_struct?*curr?=?rq->curr;
?   struct?sched_entity?*se?=?&curr->se,?*pse?=?&p->se;
?
    //?如pse的虚拟时间小于当前进程的虚拟时间 则抢占
????if?(wakeup_preempt_entity(se,?pse)?==?1)?{
??      goto?preempt;
?    }
preempt:
    //?设置一个标志 在异常处理返回的时统一调度
?   resched_curr(rq);
}

se 表示当前进程的调度实体,pse 表示 fork 出来的进程的调度实体。

调度实体这个对象也定义在进程的数据结构中。

//?include/linux/sched.h:592
struct?task_struct?{
????...
????struct?sched_entity?se;
????...
}

check_preempt_wakeup方法中的wakeup_preempt_entity:

//?kernel/sched/fair.c:6767
static?int?wakeup_preempt_entity(struct?sched_entity?*curr,?struct?sched_entity?*se)?{
?   s64?gran,?vdiff?=?curr->vruntime?-?se->vruntime;

?   if?(vdiff?<=?0)
??      return?-1;
????
    //?gran进程运行的最小时间片
?   gran?=?wakeup_gran(se);?
?   if?(vdiff?>?gran)
??      return?1;

?   return?0;
}

公平调度通过进程的优先级和历史运行情况,计算一个进程运行的虚拟时间。

虚拟时间小的进程可以抢占虚拟时间大的进程。

如果当前进程的时间片已到,当前进程的虚拟时间小于?fork?出来的进程的虚拟时间片,返回1进入到preempt执行resched_curr

//?kernel/sched/core.c:465
void?resched_curr(struct?rq?*rq)?{
????...
????set_tsk_need_resched(curr);
????...
}????

//?include/linux/sched.h:1676
static?inline?void?set_tsk_need_resched(struct?task_struct?*tsk)?{
?   set_tsk_thread_flag(tsk,TIF_NEED_RESCHED);
}

resched_curr?给当前进程设置一个标志,需要进行一次调度。

在下一次中断返回到用户空间的时候,就会进行一次调度。

futex唤醒进程设置调度标志

futex系统调用的时,也会设置需要调度的标志。

//?kernel/futex.c:3633
SYSCALL_DEFINE6(futex,?...?op,?...)?{
????...
????return?do_futex(...?op,?...);
}

用户传递的op参数是?FUTEX_WAKE_OP,需要进行唤醒操作:

//?kernel/futex.c:3573
long?do_futex(...int?op,...)?{
?int?cmd?=?op?&?FUTEX_CMD_MASK;

?switch?(cmd)?{
????????case?FUTEX_WAKE_OP:
????????????return?futex_wake_op(...);
????...
????}
????...
}

//?kernel/futex.c:1683
static?int?futex_wake_op(...)?{
????...
????wake_up_q(...);?//?:1766
????...
}

//?kernel/sched/core.c:436
void?wake_up_q(...)?{
????wake_up_process(task);
}

//?kernel/sched/core.c:1667
// 最终执行该函数
static?void?ttwu_do_wakeup(...)?{
????check_preempt_curr(...);
}

futex_wake_opfork?一样最终执行check_preempt_curr函数。

该函数做的就是给当前线程设置一个需要调度的标志,在下一次中断返回时进行一次调度。

周期调度设置标志

除了系统调用外。

内核还有一个定时调度机制

周期地调用scheduler_tick执行调度。

//?kernel/sched/core.c:3049
/*
?*?This?function?gets?called?by?the?timer?code,?with?HZ?frequency.
?*/
void?scheduler_tick(void)?{
????...
    //?当前使用的cpu
????int?cpu?=?smp_processor_id();

    //?得到cpu的进程队列
????struct?rq?*rq?=?cpu_rq(cpu);
    
    //?得到cpu前运行的进程
????struct?task_struct?*curr?=?rq->curr;
????...
????curr->sched_class->task_tick(rq,?curr,?0);
????...
}

curr->sched_class->task_tick为当前进程的调度类的方法,即上文提到的五种方法。
?

extern?const?struct?sched_class?stop_sched_class;
extern?const?struct?sched_class?dl_sched_class;
extern?const?struct?sched_class?rt_sched_class;
extern?const?struct?sched_class?fair_sched_class;
extern?const?struct?sched_class?idle_sched_class;

task_tick公平调度类方法为例:

//?kernel/sched/fair.c:10506?
const?struct?sched_class?fair_sched_class?=?{
????...????
????.task_tick?=?task_tick_fair,
????...
}

//?kernel/sched/fair.c:10030
static?void?task_tick_fair(struct?rq?*rq,?struct?task_struct?*curr,?int?queued)?{
?struct?cfs_rq?*cfs_rq;
?struct?sched_entity?*se?=?&curr->se;
????...
    //?为当前cpu上公平调度类的进程队列
????cfs_rq?=?cfs_rq_of(se);
????entity_tick(cfs_rq,?se,?queued);
????...
}

//?kernel/sched/fair.c:4179
static?void?entity_tick(struct?cfs_rq?*cfs_rq,?struct?sched_entity?*curr,?int?queued)?{
    //?更新当前进程的运行时间
?   update_curr(cfs_q);
????...
    //?更新当前进程的?load
   ?update_load_avg(cfs_rq,?curr,?UPDATE_TG);
????...
    //?如cpu有就绪进程
?   if?(cfs_rq->nr_running?>?1)
??      check_preempt_tick(cfs_rq,?curr);
}

cfs_rq->nr_running为当前cpu,公平调度类型的就绪进程、运行进程之和。

大于1表示有待调度的就绪进程。

然后调用?check_preempt_tick

该函数计算进程理想运行时间?=?调度周期*当前调度实体权重/所有实体权重。

如当前进程运行的时间超过理想运行时间,就尝试一次调度,即调用?resched_curr函数。

//?kernel/sched/fair.c:4023
static?void?check_preempt_tick(struct?cfs_rq?*cfs_rq,?struct?sched_entity?*curr)?{
?   unsigned?long?ideal_runtime,?delta_exec;
?   struct?sched_entity?*se;
????...
?   ideal_runtime?=?sched_slice(cfs_rq,?curr);
?   delta_exec?=?curr->sum_exec_runtime?-?curr->prev_sum_exec_runtime;
?   if?(delta_exec?>?ideal_runtime)?{
??      resched_curr(rq_of(cfs_rq));
?   }
????...
}
  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-06-14 22:57:06  更:2022-06-14 22:58:29 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 14:48:03-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码