04-线程设计
来源:互联网 发布:单片机设计大赛 编辑:程序博客网 时间:2024/06/03 20:41
相信上下文切换的实验你已经写出来了,不过你肯定还很多不满意的地方:
- 线程的切换太暴力
- 代码看起来一箩筐没有组织
- 看起来根本没有线程的样子
接下来,我们需要对上一节的程序进行改进,主要有以下几点:
- 封装线程创建函数
- 完成一个简单的调度算法
1. 线程结构体设计
在上一篇文章中根本没有线程结构体,说它是个线程实在是有点强人所难。我们标记线程的办法就只是使用了一个 task 数组,仅仅保存了线程的栈顶指针。现在我们要对它改良一下。
第一版的线程结构体非常简单,仅仅对之前的代码做了一个小小的扩充,除了要保存栈顶指针外,还有线程 id,线程过程函数还有栈:
#define STACK_SIZE 1024struct task_struct { int id; // 线程 id void (*th_fn)(); // 线程过程函数 int esp; // 栈顶指针 int stack[STACK_SIZE]; // 线程运行栈}
在后面,我们仍然使用 task 数组来保存线程,只不过这次的 task 数组保存的是线程结构体指针。
#define NR_TASKS 16 // 最大线程个数struct task_struct *task[NR_TASKS];
2. 封装线程创建函数
上一篇的上下文切换的实验里头,根本没有线程创建函数,而是直接在 main 函数里头构造线程要运行的栈这些东西,现在我们把那部分代码整合一下。
// tid 是传出参数,start_routine 是线程过程函数,// 这个函数和 pthread 库提供的 pthread_create 很像,// 只不过我们的线程过程函数没有参数。int thread_create(int *tid, void (*start_routine)()) { int id = -1; // 为线程分配一个结构体 struct task_struct *tsk = (struct task_struct*)malloc(sizeof(struct task_struct)); // 在任务槽中寻找一个空位置 while(++id < NR_TASKS && task[id]); // 如果没找到就返回 -1 if (id == NR_TASKS) return -1; // 将线程结构体指针放到空的任务槽中 task[id] = tsk; // 将任务槽的索引号当作线程 id 号,传回到 tid if (tid) *tid = id; // 初始化线程结构体 tsk->id = id; tsk->th_fn = start_routine; int *stack = tsk->stack; // 栈顶界限 tsk->esp = (int)(stack+STACK_SIZE-11); // 初始 switch_to 函数栈帧 stack[STACK_SIZE-11] = 7; // eflags stack[STACK_SIZE-10] = 6; // eax stack[STACK_SIZE-9] = 5; // edx stack[STACK_SIZE-8] = 4; // ecx stack[STACK_SIZE-7] = 3; // ebx stack[STACK_SIZE-6] = 2; // esi stack[STACK_SIZE-5] = 1; // edi stack[STACK_SIZE-4] = 0; // old ebp stack[STACK_SIZE-3] = (int)start; // ret to start // start 函数栈帧,刚进入 start 函数的样子 stack[STACK_SIZE-2] = 100;// ret to unknown,如果 start 执行结束,表明线程结束 stack[STACK_SIZE-1] = (int)tsk; // start 的参数 return 0;}
需要注意的是在前一篇文章中,start 函数的参数是一个整数,而现在 start 函数的参数更改为了线程结构体指针,所以 stack[STACK_SIZE-1] 的位置传入的是线程结构体指针。
另外 start 函数所做的更改也很简单,这里留作一个练习,读者可以自行分析。
3. 上下文切换函数
由于我们添加了线程结构体,修正了 task 数组的类型,所以 switch_to 函数也会稍稍变动,它的原型如下:
void switch_to(struct task_struct *next);
switch_to 的参数现在表示下一个要运行的线程的结构体指针。这里暂时先不贴代码,我会放到文章后面。
4. 简单的调度算法
在上下文的实验中,我们切换“线程”的方法相当暴力,直接就在函数里 switch_to 到指定的“线程”了,现在我们将这种方式改变一下,比方在 fun1() 函数里,我们写成这样:
void fun1() { while(1) { printf("hello, I'm fun1\n"); sleep(1); struct task_struct *next = pick(); if (next) { switch_to(next); } }}
需要注意的是 pick 函数,它的任务是从 task 数组中挑选一个合适的线程,并返回其线程结构体指针。
一旦找到了下一个线程结构体指针,就可以使用 switch_to 函数切换上下文切过去啦。所以,这里我们看看 pick 函数的工作原理。
struct task_struct *pick() { int current_id = current->id; int i = current_id; struct task_struct *next = NULL; // 寻找下一个不空的线程 while(1) { i = (i + 1) % NR_TASKS; if (task[i]) { next = task[i]; break; } } return next;}
pick 函数十分简单,它从当前线程所在的任务槽的下一个位置开始寻找不空的位置,找到就返回。这恐怕是世界上最简单的任务调度算法啦 ^_^,不过这已经能够满足我们的需求,后面我们肯定要对其进行改进。
5. 程序清单
5.1 main.c 程序
#include <stdio.h>#include <stdlib.h>#define NR_TASKS 16#define STACK_SIZE 1024struct task_struct { int id; void (*th_fn)(); int esp; // 保存 esp int stack[STACK_SIZE];};static struct task_struct init_task = {0, NULL, 0, {0}};struct task_struct *current = &init_task;struct task_struct *task[NR_TASKS] = {&init_task,};void switch_to(struct task_struct *next);struct task_struct *pick() { int current_id = current->id; int i = current_id; struct task_struct *next = NULL; while(1) { i = (i + 1) % NR_TASKS; if (task[i]) { next = task[i]; break; } } return next;}void fun1() { while(1) { printf("hello, I'm fun1\n"); sleep(1); struct task_struct *next = pick(); if (next) { switch_to(next); } }}void fun2() { while(1) { printf("hello, I'm fun2\n"); sleep(1); struct task_struct *next = pick(); if (next) { switch_to(next); } }}// 线程启动函数void start(struct task_struct *tsk) { tsk->th_fn(); task[tsk->id] = NULL; struct task_struct *next = pick(); if (next) { switch_to(next); }}int thread_create(int *tid, void (*start_routine)()) { int id = -1; struct task_struct *tsk = (struct task_struct*)malloc(sizeof(struct task_struct)); while(++id < NR_TASKS && task[id]); if (id == NR_TASKS) return -1; task[id] = tsk; if (tid) *tid = id; tsk->id = id; tsk->th_fn = start_routine; int *stack = tsk->stack; // 栈顶界限 tsk->esp = (int)(stack+STACK_SIZE-11); // 初始 switch_to 函数栈帧 stack[STACK_SIZE-11] = 7; // eflags stack[STACK_SIZE-10] = 6; // eax stack[STACK_SIZE-9] = 5; // edx stack[STACK_SIZE-8] = 4; // ecx stack[STACK_SIZE-7] = 3; // ebx stack[STACK_SIZE-6] = 2; // esi stack[STACK_SIZE-5] = 1; // edi stack[STACK_SIZE-4] = 0; // old ebp stack[STACK_SIZE-3] = (int)start; // ret to start // start 函数栈帧,刚进入 start 函数的样子 stack[STACK_SIZE-2] = 100;// ret to unknown,如果 start 执行结束,表明线程结束 stack[STACK_SIZE-1] = (int)tsk; // start 的参数 return 0;}int main() { int tid1, tid2, tid3; thread_create(&tid1, fun1); printf("create thread %d\n", tid1); thread_create(&tid2, fun2); printf("create thread %d\n", tid2); thread_create(&tid3, fun3); printf("create thread %d\n", tid3); int i = 5; while(i--) { printf("hello, I'm main\n"); sleep(1); struct task_struct *next = pick(); if (next) { switch_to(next); } }}
5.2 switch.s 程序
/*void switch_to(struct task_struct* next)*/.section .text.global switch_toswitch_to: push %ebp mov %esp, %ebp /* 更改栈帧,以便寻参 */ /* 保存现场 */ push %edi push %esi push %ebx push %edx push %ecx push %eax pushfl /* 准备切换栈 */ mov current, %eax /* 保存当前 esp, eax 是 current 基址 */ mov %esp, 8(%eax) mov 8(%ebp), %eax /* 取下一个线程结构体基址*/ mov %eax, current /* 更新 current */ mov 8(%eax), %esp /* 切换到下一个线程的栈 */ /* 恢复现场, 到这里,已经进入另一个线程环境了,本质是 esp 改变 */ popfl popl %eax popl %edx popl %ecx popl %ebx popl %esi popl %edi popl %ebp ret
5.3 编译和运行
$ gcc main.c switch.s -o main$ ./main
下面是该程序的运行结果:
图1 运行结果
6. 总结
- 线程结构体
- 线程创建函数
- 上下文切换函数
- 简单的线程调度算法
练习 1:完成本文中的实验。
练习 2:解释实验运行结果。
练习 3:start 函数相比之前的区别在哪里?
- 04-线程设计
- 设计;线程
- 线程设计模式
- ZSChatServer线程设计模型
- select线程池设计
- 线程定时器设计1
- 线程定时器设计2
- 线程定时器设计3
- 线程定时器设计4
- 监控线程设计
- linux线程设计
- 线程池典型设计
- 服务器设计系列:线程
- python线程池设计
- Java的线程设计
- 线程池服务端设计
- poco线程池设计
- 线程池的设计
- editplus快捷键汇总
- BZOJ 2017省队十连测推广赛1
- pci_bus_type/amba_bustype/platform_bus_type 下的driver使用smmu
- HashMap 内部原理
- Class bytes found but defineClass()failed for: 错误解决
- 04-线程设计
- 欢迎使用CSDN-markdown编辑器
- app 优化-提高ios app性能的建议和技巧
- android跨进程通信(一)
- Python编码规范:IF中的多行条件
- (java)leetcode-2
- 微信公众号文章页的分析与采集
- Unet同步问题
- bootstrap3教程