线程-线程调用2

前言

上一节我们用模仿函数调用的方法来实现了线程调用, 不过都是用汇编实现的, 而本节就来写用c语言来调用.

线程调用

1. 全局变量

上节汇编中threadcurrent_thread都是被定义的全局变量, 使用全局变量是为了容易可以直接在汇编中调用.

int thread[3] = {0};	// 定义3个线程
int current_thread = 0;	// 保存当前运行的线程
2. 定义线程调用函数

还记得汇编的第一句是global switch_to告诉编译器这是定义的函数, 而函数体使用汇编实现的, 声明是在另一个文件中.

void switch_to(int current_thread);	
3. 初始化线程调用

下面定义的线程调用时执行的函数, 因为后面会提及, 这里先罗列出来

void thread1()
{
    while(1)
    {
        printf("thread1\n");
        sleep(1);
        switch_to(2);
    }
}

void thread2()
{
    while(1)
    {
        printf("thread2\n");
        sleep(1);
        switch_to(1);
    }
}

void thread_start(int current_thread)
{
    if(current_thread == 1)
    {
        thread1();
    }
    else if(current_thread == 2)
    {
        thread2();
    }
}

线程在调用的时候会将esp, eax等重要的寄存器保存起来, 这样才能保证线程还能正常的被切换回来, 但是我们前面所写的汇编代码并没有申请用来保存空间啊. 怎么解决呢? 想想程序的数据都是放在内存空间中的, 而程序的部分内存空间我们都喜欢称为栈和堆, 那么我们是不是只需要将寄存器等数据放在栈或堆中就行了. 我们写简单的方法就行, 用数组来当作线程调用时的线程栈那这个问题解决了.

具体这个线程栈的空间需要多大呢? 建议最好能装进去所以寄存器就行了, 不过我写的时候线程栈为1024. 大一点总不坏.

int main()
{
    // 1024是自己设置的一个值, 1kb的大小足够线程栈使用空间了
    int thread1[1024] = {0};
    int thread2[1024] = {0};
...
}

好了, 线程定义了两个线程栈了, 也就可以支持两个线程调度了.

int main()
{
    // 1024是自己设置的一个值, 1kb的大小足够线程栈使用空间了
    int thread1[1024] = {0};
    int thread2[1024] = {0};
    thread[1] = (int)(thread1+1013);
    thread[2] = (int)(thread2+1013);
...
}  

现在在原基础上加上了两句. 这两句意思是将`thread保存线程栈栈顶的位置, 而离栈顶(1024)还有一部分的空间就是为了用来保存寄存器等重要数据. 而1013就是代表分界线, 往栈顶方向是寄存器, 往栈底方向就是可以用来存储的数据空间.

接下来就来写往栈顶方向的数据吧.

int main()
{
    // 1024是自己设置的一个值, 1kb的大小足够线程栈使用空间了
    int thread1[1024] = {0};
    int thread2[1024] = {0};
    thread[1] = (int)(thread1+1013);
    thread[2] = (int)(thread2+1013);

    /*--------- 线程1 ---------*/

    // 创建 thread1 线程
    // 初始 switch_to 函数栈帧
    int n = 1013;
    // 一下都是寄存器的值, 这里并没有用, 可以顺便赋值
   	thread1[1013] = thread1[1014] = thread1[1015] = thread1[1016] = 0;
    thread1[1017] = thread1[1018] = thread1[1019] = thread1[1020] = 0;

    // 返回的是 thread_start 的地址
    // thread_start 函数栈帧,刚进入 thread_start 函数的样子 
    thread1[1021] = (int)thread_start;

    // thread_start 执行结束,线程结束
    thread1[1022] = 0; 

    // thread_start 函数的参数
    thread1[1023] = 1; 
    ...
}

1013往下的连续用来存储通用寄存器等的值(eax, ebx, di, ZF), 但是这些数据我们实际用不到, 可以随便进行赋值, 重要的是下面的.

接下来的20行线程栈用保存一个函数的地址, 这是为了我们在线程返回时能够回到我们指定的函数(thread_start)中, 这样不就实现了指定函数的调用了吗. 23行是函数 调用结束ret返回的数值, 这里我们也没有具体使用, 可以随便修改; 26行, 也就是传入的是我们需要的传入线程函数的参数, 这里第一个线程函数向thread_start中传入的是1.

再接下来线程2也是同样的操作

int main() { ...
    /*----------- 线程2 ----------*/

    // 创建 thread2 线程
    // 初始 switch_to 函数栈帧
    n = 1013;
    for(int i = 0; i < 8; i++)
        thread2[n++] = i;   // 这一句现在要不要都是一样的, 重要的是下面的三句

    // 返回的是 thread_start 的地址
    // thread_start 函数栈帧,刚进入 thread_start 函数的样子 
    thread2[n++] = (int)thread_start;

    // thread_start 执行结束,线程结束
    thread2[n++] = 0; 

    // thread_start 函数的参数
    thread2[n++] = 2; 
    ...
}

操作基本一样, 除了18行向thread_start函数中传入的是2.

4. 线程调度

完成上面所有初始化准备工作后, 那么怎么实现我们的线程调度呢? 还记得汇编的实现的函数功能的函数名不? 对, 就是switch_to函数, 直接调用调度函数进行线程调度.

int main() { ...
	switch_to(1);  
}

所有的工作准备完成, 我们简单的线程切换就能够实现了.

5. 运行结果

直接make后生成可执行文件main, 运行程序将一直在两个线程之间进行切换.

rpz@0505:mythread1$ ./main 
thread1
thread2
thread1
thread2
thread1
thread2
thread1

小结

本节也就基本实现了对线程切换的功能了, 但是你可能对这样的写法感到不愉悦, 毕竟操作都很暴露, 没有封装性, 那么你可以适当的重新编写一个封装性更好, 或者再加上时间片起步更好, 这些都是可以完成的.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值