8.1实现两个小任务的切换(并发)

8.1实现两个小任务的切换(并发)

被一个c语言的函数小知识卡了3个小时

一个.c文件里面的函数调用另一个.c文件里面的函数

//3.c
#include<stdio.h>
#include "3.h"
void liqin(void){
	printf("我的年龄是 %d", 24);
}
//1.c
#include <stdio.h>
#include "3.c"

void lq(void) {
	printf("1123");
}

int main()
{
	liqin();
	ly();
	system("pause");
	return 0;
}

显示liqin函数重复定义了

include进来的文件什么时候和原始文件链接上的呢?

首先,预编译/预处理(preprocessing)

预编译其实简单理解就是替换那些预处理命令,对于C语言来说就是那些以"#"井号开头的一些指令,比如#include, #ifndef, #define等,它会把include的文件填充进来,确定把#ifdef之类地方是否需要编译,以及替换宏定义等

接着就是对这个预处理后的文件进行编译,但不包括汇编

我们经过编译,得到一个从C源程序到汇编源程序的文件

在经过编译将C程序翻译为汇编后,距离机器码只有一步之遥。也就是将汇编指令翻译成机器码。我们可以使用gcc命令进行汇编

实际上汇编器生成一个目标文件时,除了翻译汇编指令为机器指令以外,它会生成很多的sections, headers以及tables之类的数据,关于它生成的这些数据的作用请参考不同的目标文件格式(如ELF)。我们需要知道汇编器生成的未经过链接的目标文件时,它实际上并不知道数据和代码要在内存的什么位置运行,它更不知道那些本地引用的外部定义的全局变量和函数的位置。所以汇编器只管挖好这些坑,让链接器后面来填。

我们经过编译,得到一个从C源程序到汇编源程序的文件

include "3.h"

不是只包含头文件,而是你在链接时链接了你所include的函数所在的目标文件。你虽然没在程序里写,但是链接的时候需要指定,即使你自己没手动指定,也不代表你的集成编译环境没有为你做。

正确写法

挖个坑:为什么include "3.c"不行?自己也一知半解

#include <stdio.h>
#include "3.h"

void lq(void) {
	printf("1123");
}

int main()
{
	liqin();
	lq();
	system("pause");
	return 0;
}

遇到一个大牛,醉卧沙场

C 语言为什么只需要 include<stdio.h> 就能使用里面声明的函数?

c语言中,如果main函数的末尾没有return语句将会有什么影响?

添加任务状态段(TSS)

Intel手册里里面TSS的结构

我们需要创建一个结构体来表示这个TSS的数据,这个TSS类似于PCB(进程控制块)

//D:\code\x86\code\start\start\source\kernel\include\cpu\cpu.h
/**
 * tss描述符
 */
typedef struct _tss_t {
    uint32_t pre_link;//  没有用到
    uint32_t esp0, ss0, esp1, ss1, esp2, ss2; //和栈有关,保护模式下不同特权级的栈,ss0,esp0,代码段和栈的指针
    uint32_t cr3; //当前程序所使用的页表,与虚存有关,后面课时会用
    uint32_t eip, eflags, eax, ecx, edx, ebx, esp, ebp, esi, edi; //cpu内核寄存器相关的信息
    uint32_t es, cs, ss, ds, fs, gs; //段寄存器
    uint32_t ldt; //  没有用到
    uint32_t iomap;//  没有用到
}tss_t; //类似于PCB,进程控制块

执行空间放在内存中,每个进程在内存里面的空间是私有的,TSS里面包含了一个个寄存器

切换进程时候需要保存运行环境

函数 void init_task_entry(void)

//init.c
void init_task_entry(void) {
    int count = 0;

    for (;;) {
        log_printf("init task: %d", count++);
        task_switch_from_to(&init_task, &first_task);
    }
}

void init_main(void) {
    // int a = 3 / 0;
    log_printf("Kernel is running....");
    log_printf("Version: %s", OS_VERSION);
    log_printf("%d %d %x %c", -123, 123456, 0x12345, 'a');
        // 初始化任务
    task_init(&init_task, (uint32_t)init_task_entry, (uint32_t)&init_task_stack[1024]); //因为栈是从大到小增长的
    task_init(&first_task, 0, 0);  //init_main自己有栈
    write_tr(first_task.tss_sel); //对TR寄存器初始化

    int count = 0;
    for (;;) {
        log_printf("first task: %d", count++);
        task_switch_from_to(&first_task, &init_task);        
    }
    // irq_enable_global();
    // for (;;) {}
}

我们希望这个函数和main函数可以并发执行

初始化各个任务的TSS

我们先要到GDT里面添加上TSS Desc(描述符)

和GDT很Seg Desc格式很类似

所以套用void segment_desc_set(int selector, uint32_t base, uint32_t limit, uint16_t attr)函数

//task.h
#ifndef TASK_H
#define TASK_H

#include "comm/types.h"
#include "cpu/cpu.h"

/**
* @brief 任务控制块结构
*/
typedef struct _task_t {
   tss_t tss;				// 任务的TSS段
   uint16_t tss_sel;		// tss选择子
}task_t;

int task_init (task_t *task, uint32_t entry, uint32_t esp);
void task_switch_from_to (task_t * from, task_t * to);

#endif
//cpu.h
#define SEG_TYPE_TSS      	(9 << 0)		// 和段描述符不一样的地方,表示32位TSS
#define EFLAGS_IF           (1 << 9)
#define EFLAGS_DEFAULT      (1 << 1)
//task.c
static int tss_init (task_t * task, uint32_t entry, uint32_t esp) { //PCB入口地址,程序入口地址和栈顶指针
    // tss段初始化
	int tss_sel = gdt_alloc_desc(); 
	if (tss_sel < 0) { //分配失败的话
        log_printf("alloc tss failed.\n");
        return -1;
    } 
	segment_desc_set(tss_sel, (uint32_t)&task->tss, sizeof(tss_t), 
        SEG_P_PRESENT | SEG_DPL0 | SEG_TYPE_TSS);
    kernel_memset(&task->tss, 0, sizeof(tss_t)); //先整个清0,后面再单独设置
    task->tss.eip = entry; //当前执行指令的位置
    task->tss.esp = task->tss.esp0 = esp; //程序需要用到的栈顶指针,esp0不懂
    task->tss.ss0 = KERNEL_SELECTOR_DS; //段寄存器,我们用的是平坦模型,全局一个代码段一个数据段
    task->tss.eflags = EFLAGS_DEFAULT | EFLAGS_IF; //恢复程序运行环境以后需要我们手动打开中断,TSS保存寄存器中的值时候,硬件自动关中断,如下图(王道)
    task->tss.es = task->tss.ss = task->tss.ds 
            = task->tss.fs = task->tss.gs = KERNEL_SELECTOR_DS;   // 暂时写死,我们用的是平坦模型
    task->tss.cs = KERNEL_SELECTOR_CS;    // 暂时写死
    task->tss.iomap = 0; //

	task->tss_sel = tss_sel;
    return 0;
}

/**
 * @brief 初始化任务
 */
int task_init (task_t *task, uint32_t entry, uint32_t esp) {
    ASSERT(task != (task_t *)0);

    tss_init(task, entry, esp);
    return 0;
}

//init.c
static task_t first_task;       // 第一个任务
static uint32_t init_task_stack[1024];	// 给init_task_entry函数设置的空闲任务堆栈
static task_t init_task;

void init_task_entry(void) {
    int count = 0;

    for (;;) {
        log_printf("init task: %d", count++);
        task_switch_from_to(&init_task, &first_task);
    }
}

void init_main(void) {
    // int a = 3 / 0;
    log_printf("Kernel is running....");
    log_printf("Version: %s", OS_VERSION);
    log_printf("%d %d %x %c", -123, 123456, 0x12345, 'a');
        // 初始化任务
    task_init(&init_task, (uint32_t)init_task_entry, (uint32_t)&init_task_stack[1024]); //因为栈是从大到小增长的,所以从1024开始
    task_init(&first_task, 0, 0);  //init_main自己有栈
    write_tr(first_task.tss_sel); //对TR寄存器初始化

    int count = 0;
    for (;;) {
        log_printf("first task: %d", count++);
        task_switch_from_to(&first_task, &init_task);        
    }
    // irq_enable_global();
    // for (;;) {}
}

int_main自己自带一个栈,所以我们要手动给init_task_entry(void)一个栈,定义一个数组,每个单元大小为4字节

如图,我们还要初始化一下TR寄存器,每次TR寄存器存的都是正在运行的进程的TSS信息

为什么栈那么特殊需要自己手动创建,堆为什么不需要手动创建?

我的理解是堆是malloc创建的,不算所有进程都有,自己可以指定地址malloc,但是栈肯定是要有的

write_tr(first_task.tss_sel);

//D:\code\x86\code\start\start\source\comm\cpu_instr.h
static inline void write_tr (uint32_t tss_selector) {
    __asm__ __volatile__("ltr %%ax"::"a"(tss_selector));
} //写TR寄存器

然后需要一个任务切换函数来实现任务切换

用汇编JMP来实现

//D:\code\x86\code\start\start\source\kernel\core\task.c
void task_switch_from_to (task_t * from, task_t * to) {
    switch_to_tss(to->tss_sel);
}
//D:\code\x86\code\start\start\source\kernel\cpu\cpu.c
void switch_to_tss (uint32_t tss_selector) {
    far_jump(tss_selector, 0); //偏移量为0,这个函数在内联汇编文件里面有实现
}

所以在init.c里面

void init_task_entry(void) {
    int count = 0;

    for (;;) {
        log_printf("init task: %d", count++);
        task_switch_from_to(&init_task, &first_task);
    }
}
```
```
```
void init_main(void) {
    ```
    ```
    ```
    int count = 0;
    for (;;) {
        log_printf("first task: %d", count++);
        task_switch_from_to(&first_task, &init_task);        
    }
}

测试

成功实现多进程

  • 17
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值