Linux任务切换后清理所有资源函数finish_task_switch的实现
文章目录
- 一、释放页全局目录`mm_free_pgd`
- 1.函数结构分析
- 2.PAE(物理地址扩展)情况处理
- 3.非PAE情况处理
- 4.内存缓存管理
- 二、销毁进程 LDT上下文`destroy_context`
- 1.函数整体分析
- 2.检查并清理当前活动的LDT
- 3.释放LDT内存
- 4.重置上下文状态
- 三、释放内存描述符`__mmdrop`
- 1.函数定义和参数
- 2.安全检查
- 3.释放页全局目录(PGD)
- 4.销毁架构特定上下文
- 5.释放内存描述符本身
- 四、验证和释放安全上下文`security_task_free`
- 1.主要函数:task_free_security
- 2.安全检查
- 3.清理安全信息
- 4.封装函数:security_task_free
- 五、释放用户相关的所有资源`free_uid`
- 1.辅助函数:uid_hash_remove
- 2.主函数:free_uid
- 3.清理用户结构
- 4.最终释放
- 5.使用场景:
- 六、组信息释放`put_group_info`
- 1.主函数:groups_free
- 2.释放动态分配的块
- 3.释放组信息结构本身
- 4.引用计数封装宏
- 七、通知链机制`notifier_call_chain`
- 1.函数定义和初始化
- 2.链表遍历循环
- 3.停止条件检查
- 4.移动到下一个节点
- 5.最终返回
- 八、任务释放通知分发给注册的分析器`profile_handoff_task`
- 1.函数定义和参数
- 2.加锁保护
- 3.调用通知链
- 4.解锁和返回
- 九、释放任务结构`free_task`
- 1.宏定义
- 2.主函数:free_task
- 十、释放任务相关所有资源`__put_task_struct`
- 1.函数定义和参数
- 2.安全检查阶段
- 3.资源清理阶段
- 4.任务移交和最终释放
- 十一、任务切换完成时清理`finish_task_switch`
- 1.宏定义和辅助函数
- 2.主函数:finish_task_switch
- 3.架构切换完成和资源清理
- 4.内存管理特殊情况
- 4.1.内核线程的内存管理
- 5.并发安全考虑
- 5.1.锁的保护范围
一、释放页全局目录mm_free_pgd
void pgd_free(pgd_t *pgd)
{int i;if (PTRS_PER_PMD > 1)for (i = 0; i < USER_PTRS_PER_PGD; ++i)kmem_cache_free(pmd_cache, (void *)__va(pgd_val(pgd[i])-1));kmem_cache_free(pgd_cache, pgd);
}
static inline void mm_free_pgd(struct mm_struct * mm)
{pgd_free(mm->pgd);
}
这是一个用于释放页全局目录(Page Global Directory, PGD)的内核函数
1.函数结构分析
void pgd_free(pgd_t *pgd)
{int i;
参数说明:
pgd_t *pgd
:指向要释放的PGD表的指针- PGD是x86架构中页表的顶级结构,指向进程的地址空间
2.PAE(物理地址扩展)情况处理
#define PGDIR_SHIFT 22 // 每个 PGD 条目覆盖 2^22 字节
#define PGDIR_SIZE (1UL << PGDIR_SHIFT)
#define __PAGE_OFFSET (0xC0000000UL) // 内核空间起始地址
#define PAGE_OFFSET ((unsigned long)__PAGE_OFFSET)
#define TASK_SIZE (PAGE_OFFSET) // 用户空间最大地址
#define USER_PTRS_PER_PGD (TASK_SIZE/PGDIR_SIZE) // 用户空间占用的 PGD 条目数
#define __va(x) ((void *)((unsigned long)(x)+PAGE_OFFSET))
#define pgd_val(x) ((x).pgd)if (PTRS_PER_PMD > 1)for (i = 0; i < USER_PTRS_PER_PGD; ++i)kmem_cache_free(pmd_cache, (void *)__va(pgd_val(pgd[i])-1));
PAE模式详解:
PTRS_PER_PMD > 1
:检查是否启用PAE模式,二级页表=1
,三级页表=512
- PAE模式下,PGD包含指向PMD(Page Middle Directory)的指针,需要单独释放
USER_PTRS_PER_PGD
:用户空间PGD条目数量,它的值取决于用户空间的虚拟地址范围(TASK_SIZE
)和每个 PGD 条目覆盖的地址空间大小(PGDIR_SIZE
)
内存释放逻辑:
pgd_val(pgd[i])
:获取PGD条目的物理地址__va()
:将物理地址转换为虚拟地址-1
:PGD条目包含标志位,需要调整kmem_cache_free(pmd_cache, ...)
:释放PMD缓存对象
3.非PAE情况处理
/* in the non-PAE case, clear_page_tables() clears user pgd entries */// 在非PAE情况下,clear_page_tables()已经清理了用户PGD条目kmem_cache_free(pgd_cache, pgd);
}
非PAE模式:
- 两级页表结构
- 用户空间条目已被其他函数清理
- 直接释放PGD缓存对象
4.内存缓存管理
使用的缓存:
pmd_cache
:PMD页中间目录对象的SLAB缓存pgd_cache
:PGD页全局目录对象的SLAB缓存
SLAB分配器优势:
- 快速分配/释放固定大小的内核对象
- 减少内存碎片
二、销毁进程 LDT上下文destroy_context
void destroy_context(struct mm_struct *mm)
{if (mm->context.size) {if (mm == current->active_mm)clear_LDT();if (mm->context.size*LDT_ENTRY_SIZE > PAGE_SIZE)vfree(mm->context.ldt);elsekfree(mm->context.ldt);mm->context.size = 0;}
}
这是一个用于销毁进程 LDT(局部描述符表)上下文的函数
1.函数整体分析
void destroy_context(struct mm_struct *mm)
{// 检查该内存描述符是否有LDT上下文if (mm->context.size) {
参数说明:
struct mm_struct *mm
:进程的内存描述符指针mm->context
:包含架构特定的上下文信息(如LDT)
2.检查并清理当前活动的LDT
// 如果这个内存描述符是当前运行进程的活动内存描述符if (mm == current->active_mm)clear_LDT(); // 清除当前CPU的LDT
关键点:
current->active_mm
:当前运行进程使用的内存描述符- 如果正在销毁的是当前活动的LDT,需要立即清除CPU的LDTR寄存器
- 这防止了使用已释放的LDT描述符
3.释放LDT内存
// 根据LDT大小决定使用vfree还是kfreeif (mm->context.size*LDT_ENTRY_SIZE > PAGE_SIZE)vfree(mm->context.ldt); // 大块内存使用vfreeelsekfree(mm->context.ldt); // 小块内存使用kfree
内存分配策略:
LDT_ENTRY_SIZE
:每个LDT条目的大小(通常是8字节)PAGE_SIZE
:页面大小(通常是4KB)- 超过一页:使用
vfree()
(虚拟连续内存) - 一页以内:使用
kfree()
(普通内核内存)
4.重置上下文状态
// 将LDT大小设为0,标记LDT已释放mm->context.size = 0;}
}
状态清理:
- 设置
size = 0
表示没有有效的LDT - 防止重复释放或其他误操作
三、释放内存描述符__mmdrop
#define free_mm(mm) (kmem_cache_free(mm_cachep, (mm)))void fastcall __mmdrop(struct mm_struct *mm)
{BUG_ON(mm == &init_mm);mm_free_pgd(mm);destroy_context(mm);free_mm(mm);
}
这是一个用于彻底释放内存描述符(mm_struct
)的核心函数
1.函数定义和参数
void fastcall __mmdrop(struct mm_struct *mm)
{
关键点:
fastcall
:表示使用寄存器传递参数struct mm_struct *mm
:要释放的内存描述符指针- 这个函数完成内存描述符的最终释放
2.安全检查
BUG_ON(mm == &init_mm);
作用:
- 防止释放初始化内存描述符
init_mm
init_mm
是内核的全局内存描述符,用于内核线程和初始化过程- 如果尝试释放
init_mm
,会触发内核 bug 检查(系统崩溃)
3.释放页全局目录(PGD)
mm_free_pgd(mm);
作用: 释放进程的页表结构
调用链:
mm_free_pgd(mm)→ pgd_free(mm->pgd) // 释放PGD及其下级页表
释放的内容:
- PGD(页全局目录)
- PMD(页中间目录)- 在PAE模式下
4.销毁架构特定上下文
destroy_context(mm);
作用: 释放架构特定的资源,主要是 LDT
内部操作:
destroy_context(mm)→ if (mm == current->active_mm) clear_LDT() // 清除当前LDT→ vfree/kfree(mm->context.ldt) // 释放LDT内存→ mm->context.size = 0 // 重置上下文大小
5.释放内存描述符本身
free_mm(mm);
}
宏展开:
#define free_mm(mm) (kmem_cache_free(mm_cachep, (mm)))
作用: 将 mm_struct
对象返回给 SLAB 分配器
SLAB 缓存机制:
mm_cachep
:专门用于分配mm_struct
对象的缓存kmem_cache_free()
:将对象返回缓存,供后续分配重用
四、验证和释放安全上下文security_task_free
static void task_free_security(struct task_struct *task)
{struct task_security_struct *tsec = task->security;if (!tsec || tsec->magic != SELINUX_MAGIC)return;task->security = NULL;kfree(tsec);
}static inline void security_task_free (struct task_struct *p)
{security_ops->task_free_security (p);
}
这是一个 SELinux 安全模块中的任务释放安全钩子函数
1.主要函数:task_free_security
static void task_free_security(struct task_struct *task)
{// 获取任务的安全结构指针struct task_security_struct *tsec = task->security;
参数说明:
struct task_struct *task
:要释放安全信息的任务结构task->security
:指向任务安全相关数据的指针(SELinux 特定)
2.安全检查
// 检查安全结构是否存在且魔数匹配if (!tsec || tsec->magic != SELINUX_MAGIC)return;
安全验证逻辑:
!tsec
:如果任务没有安全结构,直接返回tsec->magic != SELINUX_MAGIC
:验证魔数是否匹配,防止内存损坏或错误指针SELINUX_MAGIC
:是一个预定义的魔数,用于验证结构完整性
3.清理安全信息
// 清空任务的安全指针task->security = NULL;// 释放安全结构内存kfree(tsec);
}
资源清理步骤:
- 断开连接:将
task->security
设为 NULL,防止悬空指针 - 释放内存:使用
kfree()
释放安全结构占用的内存
4.封装函数:security_task_free
static inline void security_task_free(struct task_struct *p)
{security_ops->task_free_security(p);
}
static inline
:内联函数,减少函数调用开销security_ops
:指向安全操作函数表的指针- 通过函数指针间接调用,支持不同的安全模块
五、释放用户相关的所有资源free_uid
static inline void uid_hash_remove(struct user_struct *up)
{list_del(&up->uidhash_list);
}void free_uid(struct user_struct *up)
{if (up && atomic_dec_and_lock(&up->__count, &uidhash_lock)) {uid_hash_remove(up);key_put(up->uid_keyring);key_put(up->session_keyring);kmem_cache_free(uid_cachep, up);spin_unlock(&uidhash_lock);}
}
这是一个用于管理用户结构(user_struct
)引用计数和释放的函数
1.辅助函数:uid_hash_remove
static inline void uid_hash_remove(struct user_struct *up)
{list_del(&up->uidhash_list);
}
作用: 从用户哈希表中移除用户结构
关键点:
list_del(&up->uidhash_list)
:从内核链表中删除节点uidhash_list
:用户结构在全局哈希表中的链表节点
2.主函数:free_uid
void free_uid(struct user_struct *up)
{// 检查用户结构是否存在,并原子性地减少引用计数if (up && atomic_dec_and_lock(&up->__count, &uidhash_lock)) {
条件检查逻辑:
up
:确保用户结构指针有效atomic_dec_and_lock(&up->__count, &uidhash_lock)
:- 原子性地减少引用计数
- 如果计数变为0,获取自旋锁并返回true
- 如果计数不为0,返回false
3.清理用户结构
// 从哈希表中移除用户结构uid_hash_remove(up);// 释放用户相关的密钥环key_put(up->uid_keyring);key_put(up->session_keyring);
资源释放:
uid_hash_remove(up)
:从全局用户哈希表中断开连接key_put(up->uid_keyring)
:减少用户密钥环的引用计数key_put(up->session_keyring)
:减少会话密钥环的引用计数
4.最终释放
// 释放用户结构内存kmem_cache_free(uid_cachep, up);// 释放自旋锁spin_unlock(&uidhash_lock);}
}
内存管理:
kmem_cache_free(uid_cachep, up)
:将对象返回SLAB缓存uid_cachep
:专门用于分配user_struct
的SLAB缓存spin_unlock(&uidhash_lock)
:释放保护哈希表的锁
5.使用场景:
场景1:进程退出
- 最后一个使用该user_struct的进程退出
- 引用计数降为0,触发完全释放
场景2:用户会话结束
- 所有属于该用户的进程都退出
- 用户结构被彻底清理
场景3:凭证切换
- 进程改变其用户身份
- 减少旧用户结构的引用计数
六、组信息释放put_group_info
void groups_free(struct group_info *group_info)
{if (group_info->blocks[0] != group_info->small_block) {int i;for (i = 0; i < group_info->nblocks; i++)free_page((unsigned long)group_info->blocks[i]);}kfree(group_info);
}#define put_group_info(group_info) do { \if (atomic_dec_and_test(&(group_info)->usage)) \groups_free(group_info); \
} while (0)
这是一个用于管理组信息(group_info
)引用计数和释放的函数
1.主函数:groups_free
void groups_free(struct group_info *group_info)
{// 检查是否使用了动态分配的大块内存if (group_info->blocks[0] != group_info->small_block) {
条件判断逻辑:
- 比较第一个块指针是否指向内嵌的小块内存
group_info->small_block
:是内嵌在group_info
结构中的小内存块- 如果不相等,说明使用了动态分配的页面
2.释放动态分配的块
// 需要释放动态分配的页面int i;for (i = 0; i < group_info->nblocks; i++)free_page((unsigned long)group_info->blocks[i]);}
内存释放细节:
group_info->nblocks
:使用的内存块数量free_page((unsigned long)group_info->blocks[i])
:释放每个页面free_page()
:用于释放单个页面(4KB)的内核函数
3.释放组信息结构本身
// 释放组信息结构kfree(group_info);
}
最终清理: 使用kfree()
释放group_info
结构本身
4.引用计数封装宏
#define put_group_info(group_info) do { \if (atomic_dec_and_test(&(group_info)->usage)) \groups_free(group_info); \
} while (0)
宏设计分析:
atomic_dec_and_test(&(group_info)->usage)
:原子性地减少引用计数并测试是否为0- 只有当最后一个引用被释放时(计数为0),才调用
groups_free
do { ... } while (0)
:确保宏在使用时像单个语句一样工作
七、通知链机制notifier_call_chain
int notifier_call_chain(struct notifier_block **n, unsigned long val, void *v)
{int ret=NOTIFY_DONE;struct notifier_block *nb = *n;while(nb){ret=nb->notifier_call(nb,val,v);if(ret&NOTIFY_STOP_MASK){return ret;}nb=nb->next;}return ret;
}
这是一个通知链(notifier chain)的核心遍历函数,用于依次调用所有注册的通知回调
1.函数定义和初始化
int notifier_call_chain(struct notifier_block **n, unsigned long val, void *v)
{int ret = NOTIFY_DONE;struct notifier_block *nb = *n;
参数说明:
struct notifier_block **n
:指向通知链头指针的指针unsigned long val
:事件值或动作类型void *v
:传递给回调函数的通用数据指针
初始化:
ret = NOTIFY_DONE
:默认返回值,表示没有处理程序关心该事件nb = *n
:获取链表的第一个节点
2.链表遍历循环
while (nb){// 调用当前节点的回调函数ret = nb->notifier_call(nb, val, v);
遍历逻辑:
while (nb)
:循环遍历链表,直到 NULL 结束nb->notifier_call(nb, val, v)
:调用当前通知块的回调函数
3.停止条件检查
if (ret & NOTIFY_STOP_MASK){return ret;}
停止掩码检查:
NOTIFY_STOP_MASK
:通常定义为0x8000
- 如果回调返回值包含停止掩码,立即终止遍历并返回
- 这允许回调函数阻止后续处理程序被调用
4.移动到下一个节点
nb = nb->next;}
链表前进:
nb = nb->next
:移动到链表中的下一个通知块- 继续循环,直到所有通知块都被处理或遇到停止条件
5.最终返回
return ret;
}
返回值:
- 返回最后一个回调函数的返回值
- 如果没有回调被调用,返回
NOTIFY_DONE
八、任务释放通知分发给注册的分析器profile_handoff_task
int profile_handoff_task(struct task_struct * task)
{int ret;read_lock(&handoff_lock);ret = notifier_call_chain(&task_free_notifier, 0, task);read_unlock(&handoff_lock);return (ret == NOTIFY_OK) ? 1 : 0;
}
这是一个使用通知链(notifier chain)机制的任务移交分析函数
1.函数定义和参数
int profile_handoff_task(struct task_struct * task)
{int ret;
参数说明:
struct task_struct *task
:要进行分析的任务指针- 返回:整数,表示分析结果(1=成功处理,0=未处理)
2.加锁保护
read_lock(&handoff_lock);
锁机制:
handoff_lock
:读写锁,保护通知链的并发访问read_lock()
:获取读锁,允许多个读者同时访问- 确保在遍历通知链时链结构不会被修改
3.调用通知链
ret = notifier_call_chain(&task_free_notifier, 0, task);
通知链调用详解:
&task_free_notifier
:任务释放通知链头指针0
:事件类型或标志task
:传递给所有通知回调函数的参数
4.解锁和返回
read_unlock(&handoff_lock);return (ret == NOTIFY_OK) ? 1 : 0;
}
返回值逻辑:
ret == NOTIFY_OK
:返回 1,表示有分析器成功处理了任务- 其他情况:返回 0,表示没有分析器处理或处理失败
九、释放任务结构free_task
#define free_thread_info(info) kfree(info)
#define free_task_struct(tsk) kmem_cache_free(task_struct_cachep, (tsk))
void free_task(struct task_struct *tsk)
{free_thread_info(tsk->thread_info);free_task_struct(tsk);
}
这是一个用于彻底释放任务结构(task_struct
)及其相关资源的函数
1.宏定义
#define free_thread_info(info) kfree(info)
#define free_task_struct(tsk) kmem_cache_free(task_struct_cachep, (tsk))
宏定义详解:
-
free_thread_info(info)
:- 使用
kfree()
释放线程信息结构 info
:指向thread_info
结构的指针kfree()
:用于释放普通内核内存
- 使用
-
free_task_struct(tsk)
:- 使用
kmem_cache_free()
释放任务结构 task_struct_cachep
:专门用于分配task_struct
的 SLAB 缓存kmem_cache_free()
:将对象返回给 SLAB 分配器
- 使用
2.主函数:free_task
void free_task(struct task_struct *tsk)
{// 释放线程信息结构free_thread_info(tsk->thread_info);// 释放任务结构本身free_task_struct(tsk);
}
十、释放任务相关所有资源__put_task_struct
void __put_task_struct(struct task_struct *tsk)
{WARN_ON(!(tsk->exit_state & (EXIT_DEAD | EXIT_ZOMBIE)));WARN_ON(atomic_read(&tsk->usage));WARN_ON(tsk == current);if (unlikely(tsk->audit_context))audit_free(tsk);security_task_free(tsk);free_uid(tsk->user);put_group_info(tsk->group_info);if (!profile_handoff_task(tsk))free_task(tsk);
}
1.函数定义和参数
void __put_task_struct(struct task_struct *tsk)
{
参数说明:
struct task_struct *tsk
:要释放的任务结构指针
2.安全检查阶段
WARN_ON(!(tsk->exit_state & (EXIT_DEAD | EXIT_ZOMBIE)));WARN_ON(atomic_read(&tsk->usage));WARN_ON(tsk == current);
三个关键检查:
-
退出状态验证:
WARN_ON(!(tsk->exit_state & (EXIT_DEAD | EXIT_ZOMBIE)));
- 确保任务处于正确的退出状态(EXIT_DEAD 或 EXIT_ZOMBIE)
-
引用计数验证:
WARN_ON(atomic_read(&tsk->usage));
- 检查任务的引用计数是否为0
- 确保没有其他地方仍在引用这个任务结构
-
当前任务检查:
WARN_ON(tsk == current);
- 防止释放当前正在运行的任务
3.资源清理阶段
if (unlikely(tsk->audit_context))audit_free(tsk);
审计上下文清理:
tsk->audit_context
:任务的审计上下文指针audit_free(tsk)
:释放审计相关的资源unlikely()
:提示编译器审计上下文不常见,优化分支预测
security_task_free(tsk);
安全子系统清理:
- 调用安全框架的任务释放钩子函数
- 释放SELinuxr等安全模块的任务安全上下文
- 确保安全相关的资源被正确清理
free_uid(tsk->user);
用户结构引用释放:
tsk->user
:指向user_struct
的指针free_uid()
:减少用户结构的引用计数,如果为0则释放- 管理用户资源的共享和释放
put_group_info(tsk->group_info);
组信息引用释放:
tsk->group_info
:任务的组信息结构put_group_info()
:减少组信息的引用计数,如果为0则释放
4.任务移交和最终释放
if (!profile_handoff_task(tsk))free_task(tsk);
}
性能分析和最终释放:
-
profile_handoff_task(tsk)
:- 返回1表示分析器接管了任务的释放,返回0表示没有分析器接管
-
条件释放:
- 如果没有分析器接管(返回0),立即释放任务
- 如果有分析器接管(返回1),分析器会在完成分析后负责释放
十一、任务切换完成时清理finish_task_switch
# define finish_arch_switch(rq, next) spin_unlock_irq(&(rq)->lock)
#define put_task_struct(tsk) \
do { if (atomic_dec_and_test(&(tsk)->usage)) __put_task_struct(tsk); } while(0)
static inline void mmdrop(struct mm_struct * mm)
{if (atomic_dec_and_test(&mm->mm_count))__mmdrop(mm);
}
static void finish_task_switch(task_t *prev)__releases(rq->lock)
{runqueue_t *rq = this_rq();struct mm_struct *mm = rq->prev_mm;unsigned long prev_task_flags;rq->prev_mm = NULL;prev_task_flags = prev->flags;finish_arch_switch(rq, prev);if (mm)mmdrop(mm);if (unlikely(prev_task_flags & PF_DEAD))put_task_struct(prev);
}
这是一个任务切换完成时的清理函数
1.宏定义和辅助函数
# define finish_arch_switch(rq, next) spin_unlock_irq(&(rq)->lock)
#define put_task_struct(tsk) \
do { if (atomic_dec_and_test(&(tsk)->usage)) __put_task_struct(tsk); } while(0)
static inline void mmdrop(struct mm_struct * mm)
{if (atomic_dec_and_test(&mm->mm_count))__mmdrop(mm);
}
宏和辅助函数说明:
finish_arch_switch
:完成架构特定的切换,释放运行队列锁put_task_struct
:安全释放任务结构的宏,使用引用计数mmdrop
:安全释放内存描述符的内联函数,使用引用计数
2.主函数:finish_task_switch
static void finish_task_switch(task_t *prev)__releases(rq->lock)
{runqueue_t *rq = this_rq();struct mm_struct *mm = rq->prev_mm;unsigned long prev_task_flags;rq->prev_mm = NULL;
参数和初始化:
task_t *prev
:刚刚被切换出去的任务__releases(rq->lock)
:函数注解,表示会释放运行队列锁this_rq()
:获取当前CPU的运行队列rq->prev_mm
:保存的前一个内存描述符- 立即将
rq->prev_mm
设为 NULL,防止重复使用
3.架构切换完成和资源清理
prev_task_flags = prev->flags;finish_arch_switch(rq, prev);
保存状态和完成切换:
- 保存前一个任务的标志位
finish_arch_switch(rq, prev)
:释放运行队列锁,允许其他CPU调度
if (mm)mmdrop(mm);
内存描述符清理:
- 如果存在前一个内存描述符,释放它
mmdrop()
会检查引用计数,只在最后一个引用时真正释放
if (unlikely(prev_task_flags & PF_DEAD))put_task_struct(prev);
}
任务结构释放:
- 检查前一个任务是否标记为PF_DEAD(已死亡)
unlikely()
:提示编译器这种情况很少见- 如果任务已死亡,释放其任务结构
4.内存管理特殊情况
4.1.内核线程的内存管理
- 内核线程没有自己的用户空间(mm = NULL)
- 但会借用前一个任务的active_mm
- 线程切换时会把内核线程借用的mm_struct保存在rq->prev_mm
- 当内核线程退出时,需要清理借用的mm_struct,即rq->prev_mm
5.并发安全考虑
5.1.锁的保护范围
- 在finish_arch_switch之前
- 运行队列锁被持有
- 可以安全检查prev->state和prev->flags
- 在finish_arch_switch之后
- 锁已释放,其他CPU可以调度prev任务
- 因此必须在释放锁前保存状态
prev->flags