c中的多线程环境

时间:2013-06-29 15:44:22

标签: c multithreading environment

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

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

如何在c中拉出这样的东西?我被告知它可以做“一切”。

我意识到这是一个非常普遍的问题,因此非常感谢任何有关此主题信息的链接。

由于

4 个答案:

答案 0 :(得分:5)

您可以在大多数系统上使用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;
}

答案 1 :(得分:1)

如果您想保持纯C,我认为可能能够使用setjmplongjmp,但我自己从未尝试过,而我想象一下,可能有些平台无法正常工作(即某些寄存器/其他设置未被保存)。唯一的另一种选择是将它写在汇编中。

答案 2 :(得分:0)

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

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

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

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

答案 3 :(得分:0)

根据我的经验,如果人们开始编写调度程序,那么他们开始想要网络堆栈,内存分配和文件系统之类的东西也不会太久。几乎不值得沿着这条路走下去;您最终花费更多时间编写自己的操作系统,而不是花在实际应用程序上。

你项目的第一个标题是这样,并且几乎总是值得投入现有的操作系​​统(linux,VxWorks等)。当然,这可能意味着如果CPU不能解决问题,你会遇到问题。 AVR并不是一个完整的CPU,并且适用于现有操作系统的范围从主要操作系统大多不可能到棘手,尽管有一些微小的操作系统(一些开源,参见http://en.wikipedia.org/wiki/List_of_real-time_operating_systems)。

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