简介

当多个任务需要在一个CPU(单个核心)上面运行时,这时就需要多任务的管理系统了。表面上多任务是同时在共享一个CPU,实际上是分时切换。每个任务控制CPU一段时间。

1-操作系统内核中的多任务管理 - 图1
按照上面这一张图,我们可以看到有四个任务(线程),同一时间只有一个任务在运行(这里是假设只有单个CPU单核)。那么什么时间运行哪一个任务,这就需要调度算法了。大致可以分为两种类型

  • 协作调度—调度程序在分配给它自己之前,不会占用计算线程的时间。
  • preemptive —时间片期满后,调度程序选择下一个活动线程。该线程还可以放弃其余的时间片。

下面我们来简单实现一个调度算法。

协作调度

list1.c

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #define TASK_COUNT 7
  4. struct task {
  5. void (*func)(void *);
  6. void *data;
  7. int index;
  8. };
  9. static struct task tasks[TASK_COUNT];
  10. static void scheduler(void) {
  11. int i;
  12. for (i = 0; i < TASK_COUNT; i++) {
  13. tasks[i].func(&tasks[i]);
  14. }
  15. }
  16. static void worker(void *data) {
  17. struct task *self = data;
  18. printf("data is %s,idnex == %d\n", (char *)self->data, self->index);
  19. }
  20. static struct task *task_create(void (*func)(void *), void *data) {
  21. static int i = 0;
  22. tasks[i].func = func;
  23. tasks[i].data = data;
  24. tasks[i].index = i;
  25. return &tasks[i++];
  26. }
  27. int main(void) {
  28. for (int i = 0; i < TASK_COUNT; i++) {
  29. task_create(&worker, "test");
  30. }
  31. for (;;) {
  32. scheduler();
  33. sleep(1);
  34. }
  35. return 0;
  36. }

1-操作系统内核中的多任务管理 - 图2可以看这里是按照顺序执行的,这个是太简单了。下面我们给这个增加一个标志位

list2.c

  1. #include <stdio.h>
  2. #define TASK_COUNT 7
  3. struct task {
  4. void (*func)(void *);
  5. void *data;
  6. int index;
  7. int activated;
  8. };
  9. static struct task tasks[TASK_COUNT];
  10. struct task_data {
  11. char *str;
  12. struct task *next_task;
  13. };
  14. static struct task *task_create(void (*func)(void *), void *data) {
  15. static int i = 0;
  16. tasks[i].func = func;
  17. tasks[i].data = data;
  18. return &tasks[i++];
  19. }
  20. static int task_activate(struct task *task, void *data) {
  21. task->data = data;
  22. task->activated = 1;
  23. return 0;
  24. }
  25. static int task_run(struct task *task, void *data) {
  26. task->activated = 0;
  27. task->func(data);
  28. return 0;
  29. }
  30. static void scheduler(void) {
  31. int i;
  32. int fl = 1;
  33. while (fl) {
  34. fl = 0;
  35. for (i = 0; i < TASK_COUNT; i++) {
  36. if (tasks[i].activated) {
  37. fl = 1;
  38. task_run(&tasks[i], tasks[i].data);
  39. }
  40. }
  41. }
  42. }
  43. static void worker1(void *data) {
  44. printf("%s\n", (char *)data);
  45. }
  46. static void worker2(void *data) {
  47. struct task_data *task_data;
  48. task_data = data;
  49. printf("%s\n", task_data->str);
  50. task_activate(task_data->next_task, "First activated");
  51. }
  52. int main(void) {
  53. struct task *t1, *t2;
  54. struct task_data task_data;
  55. t1 = task_create(&worker1, "First create");
  56. t2 = task_create(&worker2, "Second create");
  57. task_data.next_task = t1;
  58. task_data.str = "Second activated";
  59. task_activate(t2, &task_data);
  60. scheduler();
  61. return 0;
  62. }

运行的结果

  1. ./list2
  2. Second activated
  3. First activated

我们可以观察到,首先执行了t2任务。在t2的任务里面激活了t1任务。然后被执行。不过上面这个只有标志位,显然不太好。我们应该每一个任务都对应一个标志位

list3.c

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #define TASK_COUNT 2
  4. struct message {
  5. void *data;
  6. struct message *next;
  7. };
  8. struct task {
  9. void (*func)(void *);
  10. struct message *first;
  11. };
  12. struct task_data {
  13. char *str;
  14. struct task *next_task;
  15. };
  16. static struct task tasks[TASK_COUNT];
  17. static struct task *task_create(void (*func)(void *), void *data) {
  18. static int i = 0;
  19. tasks[i].func = func;
  20. tasks[i].first = NULL;
  21. return &tasks[i++];
  22. }
  23. static int task_activate(struct task *task, void *data) {
  24. struct message *msg;
  25. msg = malloc(sizeof(struct message));
  26. msg->data = data;
  27. msg->next = task->first;
  28. task->first = msg;
  29. return 0;
  30. }
  31. static int task_run(struct task *task, void *data) {
  32. struct message *msg = data;
  33. task->first = msg->next;
  34. task->func(msg->data);
  35. free(data);
  36. return 0;
  37. }
  38. static void scheduler(void) {
  39. int i;
  40. int fl = 1;
  41. struct message *msg;
  42. while (fl) {
  43. fl = 0;
  44. for (i = 0; i < TASK_COUNT; i++) {
  45. while (tasks[i].first) {
  46. fl = 1;
  47. msg = tasks[i].first;
  48. task_run(&tasks[i], msg);
  49. }
  50. }
  51. }
  52. }
  53. static void worker1(void *data) { printf("%s\n", (char *)data); }
  54. static void worker2(void *data) {
  55. struct task_data *task_data;
  56. task_data = data;
  57. printf("%s\n", task_data->str);
  58. task_activate(task_data->next_task, "Message 1 to first");
  59. task_activate(task_data->next_task, "Message 2 to first");
  60. }
  61. int main(void) {
  62. struct task *t1, *t2;
  63. struct task_data task_data;
  64. t1 = task_create(&worker1, "First create");
  65. t2 = task_create(&worker2, "Second create");
  66. task_data.next_task = t1;
  67. task_data.str = "Second activated";
  68. task_activate(t2, &task_data);
  69. scheduler();
  70. return 0;
  71. }

执行结果

  1. ./list3.run
  2. Second activated
  3. Message 2 to first
  4. Message 1 to first

_

抢占式调度

1-操作系统内核中的多任务管理 - 图3 这就和任务优先级有关了,如果任务1正在执行,但是另外一个需要马上执行的任务2被触发,这时就需要任务2进行抢占。如果需要执行抢占的话,就需要一个调度程序,这个调度程序需要可以执行中断当前正在执行的任务。切换到另外一个任务上面。因此我们需要每一个任务的运行环境的保存备份。比如栈的位置,当前任务所使用的一些临时变量。这些都可以统称为CPU上下文。

CPU上下文结构体

1-操作系统内核中的多任务管理 - 图4CPU的上下文在程序中被处理为一种数据结构,用于存储处理器寄存器的内部状态。上下文必须允许使处理器进入适当的状态以进行计算线程执行。一个处理器更改为另一个处理器的过程称为上下文切换。Linux 中包含上下文的数据结构为 task_struct。可以参考自己定义一个上下文的结构体。

  1. typedef unsigned int __u32;
  2. /**
  3. arch/arm/include/asm/thread_info.h
  4. */
  5. struct cpu_context_save {
  6. __u32 r4;
  7. __u32 r5;
  8. __u32 r6;
  9. __u32 r7;
  10. __u32 r8;
  11. __u32 r9;
  12. __u32 sl;
  13. __u32 fp;
  14. __u32 sp;
  15. __u32 pc;
  16. __u32 extra[2]; /* Xscale 'acc' register, etc */
  17. };

上下文切换

  1. /***
  2. ./arch/xtensa/kernel/entry.S:1699:
  3. */
  4. /*切换上下文的宏*/

参考资料

Context Switch Definition