【问题标题】:multithreaded environment in cc语言中的多线程环境
【发布时间】:2013-06-27 05:47:17
【问题描述】:

我只是想了解多线程环境,特别是如何在 c 中实现协作式环境(在 AVR 上,但出于兴趣,我想保持一般性)。

我的问题在于线程切换本身:我很确定我可以在汇编程序中编写它,将所有寄存器刷新到堆栈中,然后保存 PC 以供以后返回。

如何在 c 中完成这样的事情?我被告知它可以做“一切”。

我意识到这是一个相当笼统的问题,因此任何包含此主题信息的链接都将不胜感激。

谢谢

【问题讨论】:

  • 您将在汇编程序中编写线程切换代码的​​某些部分。没有办法在纯 C 中完成重要部分。
  • 通常,您保存 SP - PC 不会区分运行相同代码的线程。
  • 我有一些(简单的)示例代码in this answer

标签: c multithreading environment


【解决方案1】:

根据我的经验,如果人们开始编写调度程序,他们很快就会开始想要网络堆栈、内存分配和文件系统等东西。走这条路几乎不值得。你最终花在编写自己的操作系统上的时间比花在实际应用程序上的时间还多。

您的项目开始朝这个方向发展,几乎总是值得将精力投入到现有的操作系​​统(linux、VxWorks 等)中。当然,这可能意味着如果 CPU 不能满足要求,您就会遇到问题。而且 AVR 并不完全是大量的 CPU,并且在其上安装现有的操作系​​统对于主要操作系统来说从几乎不可能到棘手,尽管有一些小型操作系统(一些开源,请参阅http://en.wikipedia.org/wiki/List_of_real-time_operating_systems)。

因此,在项目开始时,您应该仔细考虑您希望如何将其发展到未来。这可能会影响您现在对 CPU 的选择,以免以后在软件中做可怕的事情。

【讨论】:

    【解决方案2】:

    如前所述,setjmp/longjmp 是标准 C,甚至在 8 位 AVR 的 libc 中也可用。他们完全按照您在汇编程序中所说的那样做:保存处理器上下文。但是必须记住,这些函数的预期目的只是在控制流中向后跳转;在任务之间切换是一种滥用。无论如何它确实工作,而且看起来它甚至经常用于各种用户级线程库——比如 GNU Pth。但是,这仍然是对预期目的的滥用,需要小心。

    正如 Chris Dodd 所说,您仍然需要为每个新任务提供一个堆栈。他使用了sigaltstack() 和其他与信号相关的函数,但这些函数在标准C 中不存在,只存在于类unix 环境中。例如,AVR libc 不提供它们。因此,作为替代方案,您可以尝试保留现有堆栈的一部分(通过声明一个大的本地数组,或使用alloca())作为新线程的堆栈。请记住,主/调度线程将继续使用它的堆栈,每个线程都使用自己的堆栈,并且它们都会像堆栈通常那样增长和收缩,因此它们需要空间来这样做而不会相互干扰。

    由于我们已经提到了类 unix 的非标准 C 机制,还有 makecontext()/swapcontext() 和家族,它们比 setjmp()/longjmp() 更强大但更难找到。名称说明了一切:context 函数让您可以管理完整的进程上下文(包括堆栈),jmp 函数让您可以随意跳转 - 您必须破解其余的。

    无论如何,对于 AVR,鉴于您可能没有操作系统可以提供帮助,也没有太多内存可以盲目保留,您最好使用汇编程序进行切换和堆栈初始化。

    【讨论】:

      【解决方案3】:

      您可以在大多数系统上使用setjmp/longjmp 执行此操作——这是我过去用于任务切换的一些代码:

      void task_switch(Task *to, int exit)
      {
      int tmp;
      int task_errno;     /* save space for errno */
      
          task_errno = errno;
          if (!(tmp = setjmp(current_task->env))) {
              tmp = exit ? (int)current_task : 1;
              current_task = to;
              longjmp(to->env, tmp); }
          if (exit) {
              /* if we get here, the stack pointer is pointing into an already
              ** freed block ! */
              abort(); }
          if (tmp != 1)
              free((void *)tmp);
          errno = task_errno;
      }
      

      这取决于sizeof(int) == sizeof(void *),以便将指针作为参数传递给 setjmp/longjmp,但这可以通过使用句柄(索引到所有任务结构的全局数组)而不是原始指针来避免,或者通过使用静态指针。

      当然,棘手的部分是为新创建的任务设置jmpbuf 对象,每个对象都有自己的堆栈。为此,您可以使用带有 sigaltstack 的信号处理程序:

      static      void                    (*tfn)(void *);
      static      void                    *tfn_arg;
      static      stack_t                 old_ss;
      static      int                     old_sm;
      static      struct sigaction        old_sa;
      
                  Task                    *current_task = 0;
      static      Task                    *parent_task;
      static      int                     task_count;
      
      static void newtask()
      {
      int  sm;
      void (*fn)(void *);
      void *fn_arg;
      
          task_count++;
          sigaltstack(&old_ss, 0);
          sigaction(SIGUSR1, &old_sa, 0);
          sm = old_sm;
          fn = tfn;
          fn_arg = tfn_arg;
          task_switch(parent_task);
          sigsetmask(sm);
          (*fn)(fn_arg);
          abort();
      }
      
      Task *task_start(int ssize, void (*_tfn)(void *), void *_arg)
      {
      Task                *volatile new;
      stack_t             t_ss;
      struct sigaction    t_sa;
      
          old_sm = sigsetmask(~sigmask(SIGUSR1));
          if (!current_task) task_init();
          tfn = _tfn;
          tfn_arg = _arg;
          new = malloc(sizeof(Task) + ssize + ALIGN);
          new->next = 0;
          new->task_data = 0;
          t_ss.ss_sp = (void *)(new + 1);
          t_ss.ss_size = ssize;
          t_ss.ss_flags = 0;
          if ((unsigned long)t_ss.ss_sp & (ALIGN-1))
              t_ss.ss_sp = (void *)(((unsigned long)t_ss.ss_sp+ALIGN) & ~(ALIGN-1));
          t_sa.sa_handler = newtask;
          t_sa.sa_mask = ~sigmask(SIGUSR1);
          t_sa.sa_flags = SA_ONSTACK|SA_RESETHAND;
          sigaltstack(&t_ss, &old_ss);
          sigaction(SIGUSR1, &t_sa, &old_sa);
          parent_task = current_task;
          if (!setjmp(current_task->env)) {
              current_task = new;
              kill(getpid(), SIGUSR1); }
          sigaltstack(&old_ss, 0);
          sigaction(SIGUSR1, &old_sa, 0);
          sigsetmask(old_sm);
          return new;
      }
      

      【讨论】:

        【解决方案4】:

        如果你想让它保持纯 C,我想你可能可以使用 setjmplongjmp,但我自己从未尝试过,我想可能有一些这不起作用的平台(即某些寄存器/其他设置未保存)。唯一的另一种选择是在汇编中编写它。

        【讨论】:

        • 似乎 setjmp() 是要走的路(我可能最终会用汇编程序编写它,但 setjmp() 现在看起来是一个很好的解决方案),感谢您的评论!
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-08-13
        • 1970-01-01
        • 2021-01-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多