进程切换部分代码实现

移植linux,修改的主要就是和平台相关的那部分代码.linux里面和平台相关的代码,包括很多方面,比如boot过程,系统调用,中断处理,设备驱 动,还有部分信号(软中断)处理等,进程切换也有很小一部分平台相关代码.相对其它部分,我觉得这部分平台相关代码还是相对简单的.

schedule()是uClinux中实现进程调度的函数.通过一定算法,进行调度.假设有2各进程a,b,a运行时,调用了schedule(),那 么os就要从进程就绪队列中挑选一个合适的进程,如果没有合适进程,则后面继续运行a,假设找到了合适进程b,则就要从当前进程a切换到b.这个切换过程 是在switch_to()中进行的.

switch_to()出现在schedule()函数里面。调用形式switch_to(prev, next, last);prev,next都是进程控制块task_struct的指针.prev指向当前运行的进程,next指向要切换的进程.

讲一下我移植的代码.由于代码是汇编程序,首先介绍一下cpu结构。我用的cpu采用16位指令,32位的地址和数据。有16个通用寄存器,记作r0- r15。r0作为堆栈指针寄存器sp,r1用途不固定,r2-r6作为参数传递寄存器,函数调用如果有不超过5个的参数,则参数从左至右依次放在r2- r6中。同时,r2还作为函数返回值寄存器,函数的返回值都放在r2里面。r7-r14是局部变量寄存器。r15是函数返回地址寄存器,也叫link register,存放的是function call地返回地址。

#define switch_to(prev,next,last) {           /
(1) register void *_prev __asm__ ("r2") = (prev);   /
(2) register void *_next __asm__ ("r3") = (next);   /
(3) register void *_last;                   /
(4)       __asm__ __volatile__(             /
(5)       "jbsr " SYMBOL_NAME_STR(resume) "/n/t" /
(6)       "mfcr %0, ss4"                 /
(7)         : "=r" (_last)               /
(8)         : "r" (_prev),               /
(9)           "r" (_next)                 /
(10)         : "r2", "r2", "r3");           /
(11) (last) = _last;                       /
  }
  switch_to()所做的工作其实相当于为调用resume做一些准备。(1)-(2)的意思是将变量_prev,_next分别放在寄存器r2, r3里面,他们的值分别等于prev和next,就是两个task_struct的指针。这么做是为调用resume准备好参数。第三行是声明一个寄存器 临时变量_last。

第(5)行是调用resume函数实现进程切换。jbsr是一条跳转指令,字面意思是跳入到子程序(jump to subroutine),这条指令做的工作是将现将当前pc+2保存到r15中(因为是16位指令,所以+2),相当于保存函数的返回值,然后再将pc设 置成汇编指令参数中给出的地址(就是跳转,这里就是resume的地址)。

第(6)行是将控制寄存器ss4内容放到_last对应的寄存器中。这一行指令有一些trick,先讲指令所做的操作,再讲为什么这样做。mfcr是从控 制寄存器移动到通用寄存器的指令。cpu除了有16个通用寄存器,还有16各控制寄存器。所有涉及控制寄存器的操作都要在cpu的超级用户模式下进行。 cpu模式切换通过设置第0号控制寄存器来完成。16个控制寄存器分别为cr0-cr15,其中cr0也叫psr是程序状态寄存器。cr6-cr10
也叫ss0-ss4是用于保存状态的寄存器。第(6)代码就是将ss4内容放入到变量_last所对应的寄存器中。

(7)-(10)行的意义请参考AT&T汇编。

(11)行是一个赋值,last=_last。

switch_to()所做的工作其实相当于为调用resume做一些准备。(1)-(2)的意思是将变量_prev,_next分别放在寄存器r2, r3里面,他们的值分别等于prev和next,就是两个task_struct的指针。这么做是为调用resume准备好参数。第三行是声明一个寄存器 临时变量_last。

第(5)行是调用resume函数实现进程切换。jbsr是一条跳转指令,字面意思是跳入到子程序(jump to subroutine),这条指令做的工作是将现将当前pc+2保存到r15中(因为是16位指令,所以+2),相当于保存函数的返回值,然后再将pc设 置成汇编指令参数中给出的地址(就是跳转,这里就是resume的地址)。

第(6)行是将控制寄存器ss4内容放到_last对应的寄存器中。这一行指令有一些trick,先讲指令所做的操作,再讲为什么这样做。mfcr是从控 制寄存器移动到通用寄存器的指令。cpu除了有16个通用寄存器,还有16各控制寄存器。所有涉及控制寄存器的操作都要在cpu的超级用户模式下进行。 cpu模式切换通过设置第0号控制寄存器来完成。16个控制寄存器分别为cr0-cr15,其中cr0也叫psr是程序状态寄存器。cr6-cr10
也叫ss0-ss4是用于保存状态的寄存器。第(6)代码就是将ss4内容放入到变量_last所对应的寄存器中。

(7)-(10)行的意义请参考AT&T汇编。

(11)行是一个赋值,last=_last。

其实,上面并不是一个非常优化的做法。完全可以省掉_last变量,不过当初我做时,看到m68k版本用了_last变量,而又不很清楚他的作用,为防止出错,照办了过来。其实经过后面分析,可知这个变量其实是冗余的。

那么,为什么要有(6)和(11)行的代码呢?回头可以看一下schedule()的代码,在switch_to()调用过后,schedule()中调 用了schedule_tail(prev)函数。显然prev作为参数,应该放到r2里面,所以就有了switch_to()代码的第(11)行。那么 为什么prev是来自ss4呢?

在调用resume之前,prev存放在r2中。r2中的内容属于进程的上下文,在做进程切换时,要存放在栈中。同时切换到另一个进程时,还要将另一个进 程的上下文装入到寄存器中。在装入新进程时,r2的值就会被冲掉。举个例子,比如你通过fork系统调用创建了一个新进程。我们知道,fork地返回值如 果是0就表示子进程,大于0就是父进程。对于子进程,这个栈里r2就是0(前面说过,r2用作放函数返回值),如果此时schedule选了一个经 fork后的子进程开始执行,则切换到该子进程后,其r2显然为0,当然就不是prev了。所以,我的实现是在进程切换时,将r2值存放在ss4中,切换 完毕后,再进行区别对待。如果是两个已经运行过的进程切换,则返回就返回到原来switch_to的地方。如果是新的fork出来的进程,则第一次调用, 在resume返回时,返回的是ret_from_fork,这是另外处理的。

(11)行是一个赋值,last=_last。

其实,上面并不是一个非常优化的做法。完全可以省掉_last变量,不过当初我做时,看到m68k版本用了_last变量,而又不很清楚他的作用,为防止出错,照办了过来。其实经过后面分析,可知这个变量其实是冗余的。

那么,为什么要有(6)和(11)行的代码呢?回头可以看一下schedule()的代码,在switch_to()调用过后,schedule()中调 用了schedule_tail(prev)函数。显然prev作为参数,应该放到r2里面,所以就有了switch_to()代码的第(11)行。那么 为什么prev是来自ss4呢?

在调用resume之前,prev存放在r2中。r2中的内容属于进程的上下文,在做进程切换时,要存放在栈中。同时切换到另一个进程时,还要将另一个进 程的上下文装入到寄存器中。在装入新进程时,r2的值就会被冲掉。举个例子,比如你通过fork系统调用创建了一个新进程。我们知道,fork地返回值如 果是0就表示子进程,大于0就是父进程。对于子进程,这个栈里r2就是0(前面说过,r2用作放函数返回值),如果此时schedule选了一个经 fork后的子进程开始执行,则切换到该子进程后,其r2显然为0,当然就不是prev了。所以,我的实现是在进程切换时,将r2值存放在ss4中,切换 完毕后,再进行区别对待。如果是两个已经运行过的进程切换,则返回就返回到原来switch_to的地方。如果是新的fork出来的进程,则第一次调用, 在resume返回时,返回的是ret_from_fork,这是另外处理的。

上面说了这么多,可能读者还是糊里糊涂的,我也觉得自己没说清楚,所以这里的这点实现有那么一点点trick,需要对cpu的ABI和linux的内核代码非常熟悉才行。
    (11)ldw   r7, (r0)         /* restore r7 */
    (12)ldw   r8, (r0, 4)       /* restore r8 */
    (13)addi   r0, 8
    (14)SAVE_SWITCH_STACK
    (15)lrw   r8, TASK_THREAD   /* the position of thread in task_struct */
    (16)addu   r8, r2
    (17)mfcr   r6, ss1           /* Get current usp */
    (18)stw   r6, (r8, THREAD_USP) /* Save usp in task struct */
    (19)stw   r0, (r8, THREAD_KSP) /* Save ksp in task struct */

(20)lrw   r8, TASK_THREAD
    (21)lrw   r7, SYMBOL_NAME(_current_task)
    (22)stw   r3, (r7)         /* Set new task */
    (23)addu   r8, r3           /* Pointer to thread in task_struct */

/* Set up next process to run */
    (24)ldw   r0, (r8, THREAD_KSP) /* Set next ksp */
    (25)ldw   r6, (r8, THREAD_USP) /* Set next usp */
    (26)mtcr   r6, ss1
    (27)ldw   r7, (r8, THREAD_SR)   /* Set next PSR */
    (28)mtcr   r7, psr
    (29)RESTORE_SWITCH_STACK
              ----------------
              |   r11     |
              ----------------
              |   r10     |
              ----------------
              |   r9     |
              ----------------
              |   r8     |
              ----------------
              |   r7     |
              ----------------
              |   r6     |
              ----------------
              |   r5     |
              ----------------
              |   r4     |
              ----------------
              |   r3     |
              ----------------
              |   r2     |
              ----------------
        0x1effC4 |   r1     |
              ----------------
0x1f0000和0x1effc4分别是执行过(14)前后r0的值。这是个contex save的操作。

注:lrw是立即数装入操作,addu是无符号加法,mfcr和mtcr是控制寄存器移动 操作,bclri是位清除操作,ldw是load word操作,addi是立即数加法操作。

(15)-(19)是做的栈指针保存操作。将当前进程用户栈和内核栈保存到进程控制块相应的数据结构中。linux下除了内核线程(只有内核栈)每个进程 都有2个栈,一个在用户空间一个在内核空间。如果是内核线程,则不用关心它的用户堆栈,反正不会用到,是什么值都可以。如果用户进程,则在用户进程执行系 统调用或者在用户进程执行时发生中断时,都需要从用户空间进入内核空间,在进入时,原先的用户空间栈指针就会暂时存放到ss1中。所以(17)-(18) 两行就是从ss1中取出用户空间栈指针,存入task_struct中。(15)-(19)的操作可以总结为:
  prev->thread.usp = ss1 保存用户栈指针
  prev->thread.ksp = r0   保存内核栈指针

那么有人可能会问,ss1能够保证就是正确的当前用户栈指针么?当然可以因为内核线程没有用户栈,所以这个值是什么无所谓。而对于用户进程,进入 resume的唯一入口就是schedule,而这又都是操作系统内核代码。用户进程进入内核手段就有系统调用和中断,而在系统调用和中断处理一进来就保 存了用户堆栈到ss1,所以在运行时,只要在内核里用的都是内核栈,用户栈指针不会变。

(20)-(23)执行的操作相当于_current_task = next。不再详细解释。

(24)-(28)执行的是装入新进程上下文的准备工作,也就是准备装入next了。

(24)-(25)是装入next进程的内核和用户栈。因为进程的上下文都保存在该进程的内核栈里面,所以第一步就是装入该进程的栈指针。(27)-(28)是装入next进程在切换前的状态信息。(26)就是更新ss1,现在要装入新进程了,当然就要设置新的用户栈。

(29)是装入next进程的上下文。next进程在栈里有一个和上图一样的上下文,现在就要装入。

(30)是函数调用返回。如果这个进程是刚fork出来的子进程,则上下文里面r15=ref_from_fork(可以参看copy_thread函数),否则就是返回到switch_to里面第(6)句位置。

上面就是进程切换的部分。这部分是和平台相关的。以上是我实现的代码,感觉效率并不是非常高,但功能是正确的。可能有些地方我没有讲得很清楚,有什么问题欢迎提出。

uClinux移植与分析(3)相关推荐

  1. uClinux移植概述

    uClinux移植概述 作者:姜江 E-mail:jznsmail@tom.com Blog:http://blog.csdn.net/jznsmail 1.建立开发环境     作为uClinux系 ...

  2. 【LiteOS】LiteOS移植常见问题分析

    LiteOS移植常见问题分析 1.CMSIS版本导致的问题 2.无法下载和调试代码 3.没有重定向printf导致程序死掉 4.关于OS是否托管中断 5.关于时间片轮转 6.其他问题 发现很多人在Li ...

  3. Tslib移植与分析【转】

    转自:http://blog.csdn.net/water_cow/article/details/7215308 目标平台:LOONGSON-1B开发板(mips32指令集) 编译平台:x86PC- ...

  4. Tslib移植与分析

    目标平台:LOONGSON-1B开发板(mips32指令集) 编译平台:x86PC--VMware6.5--Ubuntu10.04(下面简称"ubuntu系统")          ...

  5. Android x86的arm兼容库移植--初步分析及尝试手动移植houdini/ndk_translation

    适用于x86架构的安卓5.0以上需要root,或者离线改文件,但并非全部适用:本文仅为个人经验,未涵盖的地方需要自行探索. 手动移植Android x86的arm兼容库(houdini/ndk_tra ...

  6. linux内核分析与移植,内核分析移植

    1.make menuconfig 添加对s3c2440的支持 system  type--> s3c2440 machine--> smdk2440 2.  __lookup_machi ...

  7. 在ARMSYS(S3C44B0X开发板)上进行uClinux内核移植的总结

    标题 针对"如何在以S3C44B0X为核心的ARMSYS开发板上建立uClinux内核移植"的一个总结,其内容包括对Bootloader的功能分析和uClinux2.4.24发行版 ...

  8. (GPS移植三部曲)Linux下移植GPS应用程序之常见问题的分析与解决方法之三

    GPS应用程序移植常见问题分析与解决 ID Issue Description Analyse Causation Solution 1 导航系统一运行,就退出 使用ps命令,发现进程中cld_nav ...

  9. 嵌入式操作系统介绍分析

    一,各种嵌入式操作系统介绍 1.uC/OS-II u C / OS 是一种免费公开源代码.结构小巧.具有可剥夺实时内核的实时操作系统. μC/OS-II 的前身是μC/OS,最早出自于1992 年美国 ...

最新文章

  1. 管理敏捷需求,进行需求协作
  2. 手把手教你刷新BACKUP Bios
  3. 抓取异步数据(AJAX)笔记
  4. STM32开发 -- 蓝牙开发详解(1)
  5. mysql数据库new和old_数据库触发器中new表和old表是什么意思?
  6. C# 数据结构--排序[下]
  7. 1.1 Machine learning: what and why?
  8. STM32F103_EXTI外部中断
  9. WM下“从用户数据存储中检索信息时出错。未找到平台。”解决方案
  10. 用HTML做一份个人简历
  11. 盘点那些适合写api接口的工具
  12. jquery blockui
  13. 拥抱认知革命:区块链时代已至,临渊羡鱼不若自我革新
  14. Windows实现微信多开
  15. js对象深浅拷贝,来,试试看!
  16. EMCP 物联网云平台短信报警使用说明
  17. DDSM 数据集格式转换 LJPEG to PNG
  18. 1 区 IF:5+ | JGG 专刊征稿:人体微生物组
  19. Android8 miui9使用,小米古董机刷完安卓8.0健步如飞!感觉比MIUI9还快
  20. python中forward的作用_Pytorch学习笔记07----nn.Module类与前向传播函数forward的理解

热门文章

  1. 八层高速PCB板叠层设计
  2. 微信小程序使用后台播放器播放音乐
  3. 第一章 【教育基础知识和基本原理】
  4. QNET:APP弱网络测试专家
  5. html如何设置提示收到消息,从零开始实现一个消息提示框
  6. OpenSSL FIPS安装
  7. Revo Uninstaller专业版
  8. 跳槽穷半年,转行穷三年,死守会穷一辈子
  9. 【论文解析】Pixel-aligned Volumetric Avatars
  10. 求出本周是第几周,本周的周一和周日时间