多任务操作系统是如何切换进程

2023-05-16

多任务操作系统在并行执行多任务时,实际上是不断地在任务间进行切换的,也就是切换上文。首先要保存前一个进程的上下文,然后调度一个就绪的进程,并载入该进程的上下文,cpu开始执行该进程的代码。在切换上下文时,最重要的就是切换eip寄存器的值和esp寄存器的值,eip寄存器指向的指令即时cpu即将执行的指令,esp寄存器指向栈顶。下面我们通过一段比较简单的代码来演示一下cpu是如何切换进程的。

代码已经由孟宁老师编写好了,下载地址孟宁-mykernel

按照readme文件中的的操作打好补丁。使用mykernel-1.1文件中的mymain.c、mypcb.h、myinterrupt.c替换linux-3.9.4/mykernel中的相应的文件,然后make。

这是修改后的linux内核源码,代码太长,我们从中取重要的部分,首先来看mypcb.h,这是我们自定义的进程pcb。

/*
 *  linux/mykernel/mypcb.h
 *
 *  Kernel internal PCB types
 *
 *  Copyright (C) 2013  Mengning
 *
 */

//最大进程数量
#define MAX_TASK_NUM        4
//进程栈的大小
#define KERNEL_STACK_SIZE   1024*8

/* CPU-specific state of this task */
//该进程的cpu状态
struct Thread {
		//eip寄存器的值
    unsigned long		ip;
		//栈顶
    unsigned long		sp;
};

// 进程控制块
typedef struct PCB{
		//进程id
    int pid;
		//进程状态
    volatile long state;	/* -1 unrunnable, 0 runnable, >0 stopped */
		//进程栈,栈底是数组的最后一个元素位置
    char stack[KERNEL_STACK_SIZE];
    /* CPU-specific state of this task */
		//cpu状态
    struct Thread thread;
		//进程入口
		unsigned long	task_entry;
		//指向下一个进程的指针
    struct PCB *next;
}tPCB;

void my_schedule(void);


下面看一下mymain.c,这里包括我们手工创造的第0号进程,以及其他进程的fork();

/*
 *  linux/mykernel/mymain.c
 *
 *  Kernel internal my_start_kernel
 *
 *  Copyright (C) 2013  Mengning
 *
 */

#include "mypcb.h"
//存放进程pcb的数组
tPCB task[MAX_TASK_NUM];
//指向当前进程pcb的指针
tPCB * my_current_task = NULL;
//调度标志,为1时表示需要调度了,为0时表示不需要调度
volatile int my_need_sched = 0;
//进程代码入口
void my_process(void);


void __init my_start_kernel(void)
{
		//进程的pid即是pcb表的下标
    int pid = 0;
    int i;
    /* Initialize process 0*/
		//0号进程是手工造出来的
    task[pid].pid = pid;
		//0代表就绪态,-1代表睡眠,>0代表暂停
    task[pid].state = 0;/* -1 unrunnable, 0 runnable, >0 stopped */
		//进程入口
    task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process;
		//进程的栈指针设置为栈顶
    task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1];
		//下一个进程指向进程表中的下一项
    task[pid].next = &task[pid];
    /*fork more process */
		//把进程表填满,相当于fork()
    for(i=1;i<MAX_TASK_NUM;i++)
    {
        memcpy(&task[i],&task[0],sizeof(tPCB));
        task[i].pid = i;
        task[i].state = -1;
	//数组的最后一位作为栈基址,即栈底
        task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-1];
        task[i].next = task[i-1].next;
        task[i-1].next = &task[i];
    }
    /* start process 0 by task[0] */
    pid = 0;
		//把当前进程指针指向0号进程
    my_current_task = &task[pid];
	asm volatile(
				//%1即参数task[pid].thread.sp,赋给esp
    	"movl %1,%%esp\n\t" 	/* set task[pid].thread.sp to esp */
			//进程开始之前栈是空的,所以esp==ebp,task[pid].thread.sp压栈即把栈底压栈
    	"pushl %1\n\t" 	        /* push ebp */
			//把task[pid].thread.ip压栈
    	"pushl %0\n\t" 	        /* push task[pid].thread.ip */
			//把eip弹出,之所以这么做,是因为eip的值是不能通过movl指令设置的,但是可以使用从栈中弹出的方式为其赋值
    	"ret\n\t" 	            /* pop task[pid].thread.ip to eip */
			//同样弹出ebp,原因同上
    	"popl %%ebp\n\t"
    	: 
    	: "c" (task[pid].thread.ip),"d" (task[pid].thread.sp)	/* input c or d mean %ecx/%edx*/
	);
}   
void my_process(void)
{
    int i = 0;
    while(1)
    {
        i++;
        if(i%100000000 == 0)
        {
						//打印自己的pid
            printk(KERN_NOTICE "this is process %d -\n",my_current_task->pid);
						//需要调用进程调度函数了
            if(my_need_sched == 1)
            {
								//复位my_need_sched
                my_need_sched = 0;
								//调度,当前进程执行到这里就不再执行了,当该函数返回时,说明又调度到该进程了
        	    my_schedule();
						}
						//执行调度函数后,这个printfk是不会执行的,当执行到这里时表明又调度到这个进程执行了
        	printk(KERN_NOTICE "this is process %d +\n",my_current_task->pid);
        }     
    }
}


再来看一下myinterrupt.c

/*
 *  linux/mykernel/myinterrupt.c
 *
 *  Kernel internal my_timer_handler
 *
 *  Copyright (C) 2013  Mengning
 *
 */
#include <linux/types.h>
#include <linux/string.h>
#include <linux/ctype.h>
#include <linux/tty.h>
#include <linux/vmalloc.h>

#include "mypcb.h"

extern tPCB task[MAX_TASK_NUM];
extern tPCB * my_current_task;
extern volatile int my_need_sched;
volatile int time_count = 0;

/*
 * Called by timer interrupt.
 * it runs in the name of current running process,
 * so it use kernel stack of current running process
 */
//自定义的时钟中断处理程序
void my_timer_handler(void)
{
#if 1
		//每1000次时钟中断并且没有正在进行进程调度
		//其实这里并不一定每1000次时钟中断就会切换一次进程
		//有可能正好1000次的时候,my_need_sched为1,还没有被复位呢
    if(time_count%1000 == 0 && my_need_sched != 1)
    {
        printk(KERN_NOTICE ">>>my_timer_handler here<<<\n");
				//把需要调度标志设置为1表示需要调度了
        my_need_sched = 1;
    } 
		//时钟中断产生的次数+1
    time_count ++ ;  
#endif
    return;  	
}

//自定义的进程调度器
void my_schedule(void)
{
    tPCB * next;
    tPCB * prev;
		//如果当前进程为空,或者下一个进程为空,直接返回
    if(my_current_task == NULL 
        || my_current_task->next == NULL)
    {
    	return;
    }
    printk(KERN_NOTICE ">>>my_schedule<<<\n");
    /* schedule */
    next = my_current_task->next;
    prev = my_current_task;
    if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */
    {
    	my_current_task = next; 
    	printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);  
    	/* switch to next process */
    	asm volatile(	
						//当前进程的ebp入栈
        	"pushl %%ebp\n\t" 	    /* save ebp */
					//保存当前进程的esp
        	"movl %%esp,%0\n\t" 	/* save esp */
					//把esp恢复为下一个进程pcb内的esp
        	"movl %2,%%esp\n\t"     /* restore  esp */
					//把标号1处的地址存入pcb中的ip,以便下次该进程恢复时从此处开始执行
        	"movl $1f,%1\n\t"       /* save eip */	
					//把下一个进程pcb的eip入栈
        	"pushl %3\n\t" 
					//弹出下一个进程的eip值到寄存器
        	"ret\n\t" 	            /* restore  eip */
        	"1:\t"                  /* next process start here */
					//标号1处的地址就下面这一行代码的地址,当前进程执行到ret后就切换到下一个进程了
					//所以当前进程恢复时首先从下面这一行代码开始执行,首先把栈基址弹出
        	"popl %%ebp\n\t"
        	: "=m" (prev->thread.sp),"=m" (prev->thread.ip)
        	: "m" (next->thread.sp),"m" (next->thread.ip)
    	); 
 	
    }
    else
    {
				//新创建的进程状态在mymain.c中我们设置为-1,新进程是未就绪的,所以首先设置为就绪态
        next->state = 0;
        my_current_task = next;
        printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);
    	/* switch to new process */
    	asm volatile(	

        	"pushl %%ebp\n\t" 	    /* save ebp */
        	"movl %%esp,%0\n\t" 	/* save esp */
        	"movl %2,%%esp\n\t"     /* restore  esp */
					//新进程和已经运行过的进程的唯一区别在于,已经运行过的进程在切换时把栈基址保存到栈中了
					//新进程的栈为空,要初始化栈基址,下面这一行代码就是使用pcb中sp初始化栈基址,其他的和上面一模一样
        	"movl %2,%%ebp\n\t"     /* restore  ebp */
        	"movl $1f,%1\n\t"       /* save eip */	
        	"pushl %3\n\t" 
        	"ret\n\t" 	            /* restore  eip */
        	: "=m" (prev->thread.sp),"=m" (prev->thread.ip)
        	: "m" (next->thread.sp),"m" (next->thread.ip)
    	);          
    }   
    return;	
}


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

多任务操作系统是如何切换进程 的相关文章

  • PX4 CMakeLists.txt分析

    简单的概述 make 和 cmake 是linux UNIX系统下广泛使用的构建编译规则工具 xff0c 面对复杂庞大的工程 xff0c 各种源文件和工具文件分布在工程目录下 xff0c 如何组织和有序地编译和使用这些文件 xff0c 显然
  • iOS OC消除黄色警告⚠️ (不断的更新中...)

    开发一个项目时 xff0c 难免会产生很多警告 xff0c 一些是第三方或是老代码不再被支持造成的 xff0c 但并不影响使用 xff0c 这些警告其实可以直接隐藏掉 xff01 还有一些 警告可能是系统方法弃用 不兼容指针类型 未使用变量
  • AUTOSAR E2E & SecOC Comparison

    AUTOSAR E2E amp SecOC Comparison 前面已经介绍过了E2E 和 SecOC CMAC 了 xff0c 既然2者都可以进行数据完整性保护 xff0c 那么2者有什么区别呢 下面基于我的经验所总结 欢迎补充 xff
  • 解决:source devel/setup.bash(只在当前终端生效)的问题,使其在其他终端

    解决方法 xff1a 终端输入 xff1a gedit bashrc 文件打开后直接翻到最后面 在底部添加source catkin ws devel setup bash 保存退出即可
  • ROS与C++入门教程

    https www ncnynl com archives 201701 1279 html
  • HiChart图表统计:jsp中hichart用法以及从后台获取数据

    在做web服务器时 xff0c 用到了图表 xff0c 对一天内资源的下载量进行统计 xff0c 让数据更加的直观 上网查了很多资料 xff0c 最后发现HiChart很好用 xff0c 相对比较简单 下面作以详细介绍 xff1a 1 首先
  • [论文]欠驱动水下机器人的平面轨迹规划与跟踪控制设计

    论文 欠驱动水下机器人的平面轨迹规划与跟踪控制设计 摘要 研究了欠驱动自主水下航行器在水平面上的轨迹规划与跟踪控制的组合问题 给定光滑的 惯性的二维参考轨迹 xff0c 规划算法利用车辆动力学计算参考方向和机体固定速度 利用这些 xff0c
  • sql查询语句汇总,先撸为敬

    一 简单查询语句 group by 和having的区别 链接 二 复杂查询 1 数据分组 max min avg sum count SQL gt SELECT MAX sal MIN age AVG sal SUM sal from e
  • 最优化的基本概念

    最优化的基本概念 连续和离散优化问题无约束和约束优化问题随机和确定性优化问题线性和非线性规划问题凸和非凸优化问题全局和局部最优解优化算法 一般来说 xff0c 最优化算法研究可以分为 xff1a 构造最优化模型 确定最优化问题的类型和设计算
  • [RISCV]为RISC-V移植FreeRTOS系列之一 -- 目录结构

    前言 写这篇文章的时候 xff0c 我基本已经完成了这项工作了 xff0c 花了一周的时间来把freertos porting到Andes公司的N25 riscv core上 xff0c 本来其实是想支持国产的RT Thread xff0c
  • [RISCV]为RISC-V移植FreeRTOS系列之三 -- 时基

    前言 书接上回 xff0c 上回说到我们已经做好了准备 xff0c 所谓万事具备 xff0c 就差一场东风 xff0c 而能吹动FreeRTOS这条大船的是什么呢 xff1f 没错 xff0c 聪明的你已经猜到了 xff0c 是时基 有过其
  • [RISCV]为RISC-V移植FreeRTOS系列之四 -- 中断与trap handler

    前言 上回说到了我们已经把系统的心跳动起来了 xff0c 但是这里面还有一个问题 xff0c 我们都知道timer中断 xff0c 中断的trap怎么来的呢 这回就来解决这个事情 作者 xff1a wangyijieonline 链接 xf
  • [RTOS]uCOS、FreeRTOS、RTThread、RTX等RTOS的对比之特点

    最近正好又重新回顾了一下这几款OS xff0c 心里一直有个疑问 xff0c 明明这几款RTOS是这么像 xff0c 为什么还要搞出这么多个来呢 xff0c 最后的结论就是 xff0c 管他呢 xff0c 反正哪个用的顺手用哪个 本篇博客就
  • git submodule

    此文已由作者张磊薪授权网易云社区发布 欢迎访问网易云社区 xff0c 了解更多网易技术产品运营经验 前言 submodule 目前对 git 仓库拆分的已有实现之一 环境 git version 2 7 4 windows 1 准备工作 首
  • FreeRTOS 通信方式

    文章目录 一 消息队列二 信号量三 互斥量四 事件五 通知 一 消息队列 消息队列是一种常用于任务间通信的数据结构 xff0c 队列可以在任务与任务间 中断和任务间传递信息 读写队列均支持超时机制 1 创建队列 QueueHandle t
  • 芯片、模组、开发板的区别与联系-结合ESP32浅谈

    1 从外形说起 xff1a 1 1芯片 没错 xff0c 这块黑色的小硅片就是 芯片 本体 xff08 通常比大拇指还小 xff0c 内部集成了实现特定功能的硬件集成电路 xff09 1 2模组 由上述芯片研发的模组是这样的 xff1a 从
  • 一文读懂局域网、广域网、WLAN、WiFi的联系与区别

    1 引言 最近总有小伙伴问我 xff0c 广域网 局域网的区别与联系 WLAN与WiFi的关系 xff0c 遂写此文 xff0c 以作解答 2 广域网与局域网 广域网 xff08 Wide Area Network xff09 xff0c
  • RTOS 和裸机系统的异同-基于 ESP32 学习双核 FreeRTOS 的使用

    Learning FreeRTOS with esp32 什么是 RTOS 其本质上是运行在小型嵌入式设备上的特殊软件 系统软件 如同手机的安卓系统软件 windows 系统软件 RTOS VS 裸机系统 传统的裸机系统 xff08 无操作
  • u盘打开之后就只有一个快捷方式

    我今天也出现了这种问题 xff0c 百度一下发 现都解决不了 xff0c 然后自己尝试了一个新的方法 xff1a 其实还有一个又简单又好用又快捷的方法就是 1 只要你记得你的U盘里的任何一个文件或者文件夹的名称 xff0c 2 然后搜索U盘
  • FreeRTOS 删除任务

    FreeRTOS 删除任务 概述 任务的删除使用的 API 为 xff1a void vTaskDelete TaskHandle t xTask 任务删除主要是两种情况 xff1a 自删除 xff0c 即在任务本身的 TaskCode 中

随机推荐

  • 使用 stream buffer 传递数据

    使用 stream buffer 传递数据 概述 如前所述 xff0c 队列虽然提供了任务之间传递数据的功能 xff0c 但没有对通知机制进行优化 xff0c 即不方便实现多次采集不同长度的数据 xff0c 然后触发一次通知接收的机制 特性
  • 使用 message buffer 传递数据

    使用 message buffer 传递数据 概述 MessageBuffer xff0c 即消息缓冲区 xff0c 是在流式缓冲区的基础上实现的针对离散消息的专用通信组件 xff0c 其进一步针对 消息 进行设计改进 在 StreamBu
  • FreeRTOS 任务间通信与同步总结

    FreeRTOS 任务任务同步与数据传递 xff08 通信 xff09 总结 概述 本章主要介绍了 RTOS 系统中数据传递的机制 根据数据传递的目的 xff0c 可以分为同步 消息通信两种 其中同步是指协调程序运行的先后顺序 xff0c
  • RTOS 中 Task 之间资源共享示例

    RTOS 中 Task 之间资源共享示例 什么是共享资源 大型项目往往需要创建多个任务 xff0c 任务之间协同合作完成一个大型的功能 在前述的章节中 xff0c 我们讲述了任务间的同步与通信 xff0c 但合作与竞争总是相辅相成的 任务
  • RTOS共享资源保护-优先级反转与解决策略

    RTOS 中的优先级反转与解决策略 概述 上节讲述了可以使用二值信号量实现任务 任务之间的共享资源的保护 二值信号量的确完成了保护共享资源的任务 但在一些情况下 这种策略会带来副作用 即优先级反转 优先级反转是如何产生的 理想情况下 按照我
  • RTOS 驱动开发篇-通过 RTOS 组件实现按键驱动-优化1

    RTOS 驱动开发篇 通过 RTOS 组件实现按键驱动 优化1 概述 一个好的驱动程序需要数据关系清晰 代码可复用性高 并且便于维护 如在 RTOS 驱动开发篇 通过 RTOS 组件实现按键驱动1 中所述的那样 当前的按键驱动代码只是为了让
  • RTOS 驱动开发篇-通过 RTOS 组件实现按键驱动-优化2

    RTOS 驱动开发篇 通过 RTOS 组件实现按键驱动 优化2 概述 一个好的驱动程序需要数据关系清晰 代码可复用性高 并且便于维护 如在 RTOS 驱动开发篇 通过 RTOS 组件实现按键驱动1 中所述的那样 基础版本的按键驱动代码只是为
  • 物联网应用选择 RTOS 还是 Linux?

    物联网应用选择 RTOS 还是 Linux Linux VS RTOS xff0c 我该选哪个 xff1f 引言 在开发设备或系统时 xff0c 您需要做出的最早和最关键的决定之一就是决定它将运行哪种类型的操作系统 操作系统是基于特定硬件的
  • 嵌入式编程中的 __attribute__ 到底是什么

    嵌入式编程中的 attribute 到底是什么 相信阅读嵌入式代码的老铁经常看到一些类型定义 变量 函数有 attribute 标识符 xff0c 这个标识符号到底是做什么的 xff1f 有哪些用法 xff0c 咱们今天就来聊一聊 attr
  • opengl之glTranslatef()函数和glRotatef()函数和glLoadIdentity()函数

    glLoadIdentity 将当前的用户坐标系的原点移到了屏幕中心 xff1a 类似于一个复位操作 1 X坐标轴从左至右 xff0c Y坐标轴从下至上 xff0c Z坐标轴从里至外 2 OpenGL屏幕中心的坐标值是X和Y轴上的0 0f点
  • 一文读懂大端、小端、字节序、MSB、LSB、MSBs、LSBs

    大端 小端 字节序 MSB LSB MSBs LSBs 5分钟完全理解上述嵌入式 物联网开发中很扯蛋的几个被玩坏概念 MSB LSB 对于涉及 bit 流的概念中 MSB xff08 Most Significant Bit xff09 x
  • 飞控串口通信接入linux

    1 串口设置 2 串口接入 3 消息传输 4 消息透传 5 mavlink解析 1 mavlink库 将mavlink库添加至项目包含目录 2 常用mavlink消息 mavlink heartbeat t 心跳包 mavlink atti
  • python的函数修改外部传入的参数的问题

    span class token keyword def span span class token function test span span class token punctuation span df span class to
  • 树莓派笔记8:UDP传输视频帧

    因为我在自己笔记本电脑上没能成功安装OpenCV Contrib模块 xff0c 因此不能使用人脸识别等高级功能 xff0c 不过已经在树莓派上安装成功了 xff0c 所以我想实现把树莓派上采集的视频帧传输到PC的功能 xff0c 这样可以
  • 15.linux中的源码安装,SRPM包安装,rpmbild,spec详解

    前言 本小节会详细讲解在linux中如何进行源码编译安装 xff0c SRPM包的两种安装方式 xff0c rpmbuild spec的使用方法 文章目录 前言源码安装和卸载源码安装介绍安装gcc安装源码包Linux源码包卸载 SRPM包的
  • zephyr中消息队列和邮箱的主要区别点

    简单列一下而已 xff0c 想到什么就列了什么 xff1a 1 邮箱既可以同步也可以异步 xff0c 消息队列只可以异步 xff1b 2 邮箱包含Send和Recv两个消息队列 xff0c 消息队列仅仅包含一个用于消息传输的队列 3 邮箱不
  • Windows/Linux客户端挂载NFS共享存储

    Windows Linux客户端挂载NFS共享存储 1 Linux搭建NFS共享存储1 1 NFS概述1 2 安装并配置NFS Server1 3 启动并验证NFS Server 2 客户端挂载NFS共享存储2 1 Windows操作系统挂
  • WIN10源码编译安装QGC-V3.4

    WIN10源码编译安装QGC V3 4 20190228更新 整个安装过程的流程为 xff0c 先安装VS2015 xff0c 再安装Git 用Git来下载qgroundcontrol代码 xff0c 最后下载Qt 用Qt对qgroundc
  • ESP8266简介

    ESP8266 是一款适用于物联网和家庭自动化项目的 Wi Fi 模块 ESP8266 是一个 10元人名币的 Wi Fi 模块 它允许您像使用 Arduino 一样控制输入和输出 xff0c 但它带有 Wi Fi 因此 xff0c 它非常
  • 多任务操作系统是如何切换进程

    多任务操作系统在并行执行多任务时 xff0c 实际上是不断地在任务间进行切换的 xff0c 也就是切换上文 首先要保存前一个进程的上下文 xff0c 然后调度一个就绪的进程 xff0c 并载入该进程的上下文 xff0c cpu开始执行该进程