int task_exit(void) { FAR struct tcb_s *dtcb = (FAR struct tcb_s*)g_readytorun.head; FAR struct tcb_s *rtcb; int ret; /* Remove the TCB of the current task from the ready-to-run list. A context * switch will definitely be necessary -- that must be done by the * architecture-specific logic. * * sched_removereadytorun will mark the task at the head of the ready-to-run * with state == TSTATE_TASK_RUNNING */ (void)sched_removereadytorun(dtcb); rtcb = (FAR struct tcb_s*)g_readytorun.head; /* We are now in a bad state -- the head of the ready to run task list * does not correspond to the thread that is running. Disabling pre- * emption on this TCB and marking the new ready-to-run task as not * running (see, for example, get_errno_ptr()). * * We disable pre-emption here by directly incrementing the lockcount * (vs. calling sched_lock()). */ rtcb->lockcount++; rtcb->task_state = TSTATE_TASK_READYTORUN; /* Move the TCB to the specified blocked task list and delete it. Calling * task_terminate with non-blocking true will suppress atexit() and on-exit() * calls and will cause buffered I/O to fail to be flushed. The former * is required _exit() behavior; the latter is optional _exit() behavior. */ sched_addblocked(dtcb, TSTATE_TASK_INACTIVE); ret = task_terminate(dtcb->pid, true); rtcb->task_state = TSTATE_TASK_RUNNING; /* If there are any pending tasks, then add them to the ready-to-run * task list now */ if (g_pendingtasks.head) { (void)sched_mergepending(); } /* We can't use sched_unlock() to decrement the lock count because the * sched_mergepending() call above might have changed the task at the * head of the ready-to-run list. Furthermore, we should not need to * perform the unlock action anyway because we know that the pending * task list is empty. So all we really need to do is to decrement * the lockcount on rctb. */ rtcb->lockcount--; return ret; }
/**************************************************************************** * Name: up_block_task * * Description: * The currently executing task at the head of * the ready to run list must be stopped. Save its context * and move it to the inactive list specified by task_state. * * This function is called only from the NuttX scheduling * logic. Interrupts will always be disabled when this * function is called. * * Inputs: * tcb: Refers to a task in the ready-to-run list (normally * the task at the head of the list). It most be * stopped, its context saved and moved into one of the * waiting task lists. It it was the task at the head * of the ready-to-run list, then a context to the new * ready to run task must be performed. * task_state: Specifies which waiting task list should be * hold the blocked task TCB. * ****************************************************************************/ void up_block_task(struct tcb_s *tcb, tstate_t task_state) { /* Verify that the context switch can be performed */ if ((tcb->task_state < FIRST_READY_TO_RUN_STATE) || (tcb->task_state > LAST_READY_TO_RUN_STATE)) { warn("%s: task sched error\n", __func__); return; } else { struct tcb_s *rtcb = current_task; bool switch_needed; /* Remove the tcb task from the ready-to-run list. If we * are blocking the task at the head of the task list (the * most likely case), then a context switch to the next * ready-to-run task is needed. In this case, it should * also be true that rtcb == tcb. */ switch_needed = sched_removereadytorun(tcb); /* Add the task to the specified blocked task list */ sched_addblocked(tcb, (tstate_t)task_state); /* Now, perform the context switch if one is needed */ if (switch_needed) { struct tcb_s *nexttcb; // this part should not be executed in interrupt context if (up_interrupt_context()) { panic("%s: %d\n", __func__, __LINE__); } // If there are any pending tasks, then add them to the g_readytorun // task list now. It should be the up_realease_pending() called from // sched_unlock() to do this for disable preemption. But it block // itself, so it's OK. if (g_pendingtasks.head) { warn("Disable preemption failed for task block itself\n"); sched_mergepending(); } nexttcb = (struct tcb_s*)g_readytorun.head; // context switch up_switchcontext(rtcb, nexttcb); } } }
void up_block_task(FAR struct tcb_s *tcb, tstate_t task_state) { FAR struct tcb_s *rtcb = (FAR struct tcb_s*)g_readytorun.head; bool switch_needed; /* Verify that the context switch can be performed */ ASSERT((tcb->task_state >= FIRST_READY_TO_RUN_STATE) && (tcb->task_state <= LAST_READY_TO_RUN_STATE)); /* dbg("Blocking TCB=%p\n", tcb); */ /* Remove the tcb task from the ready-to-run list. If we * are blocking the task at the head of the task list (the * most likely case), then a context switch to the next * ready-to-run task is needed. In this case, it should * also be true that rtcb == tcb. */ switch_needed = sched_removereadytorun(tcb); /* Add the task to the specified blocked task list */ sched_addblocked(tcb, (tstate_t)task_state); /* If there are any pending tasks, then add them to the g_readytorun * task list now */ if (g_pendingtasks.head) { switch_needed |= sched_mergepending(); } /* Now, perform the context switch if one is needed */ if (switch_needed) { /* Are we in an interrupt handler? */ if (IN_INTERRUPT()) { /* Yes, then we have to do things differently. * Just copy the current registers into the OLD rtcb. */ SAVE_IRQCONTEXT(rtcb); /* Restore the exception context of the rtcb at the (new) head * of the g_readytorun task list. */ rtcb = (FAR struct tcb_s*)g_readytorun.head; /* dbg("New Active Task TCB=%p\n", rtcb); */ /* Then setup so that the context will be performed on exit * from the interrupt. */ SET_IRQCONTEXT(rtcb); } /* Copy the user C context into the TCB at the (old) head of the * g_readytorun Task list. if SAVE_USERCONTEXT returns a non-zero * value, then this is really the previously running task restarting! */ else if (!SAVE_USERCONTEXT(rtcb)) { /* Restore the exception context of the rtcb at the (new) head * of the g_readytorun task list. */ rtcb = (FAR struct tcb_s*)g_readytorun.head; /* dbg("New Active Task TCB=%p\n", rtcb); */ /* Then switch contexts */ RESTORE_USERCONTEXT(rtcb); } } }
void up_block_task(struct tcb_s *tcb, tstate_t task_state) { struct tcb_s *rtcb = this_task(); bool switch_needed; /* Verify that the context switch can be performed */ DEBUGASSERT((tcb->task_state >= FIRST_READY_TO_RUN_STATE) && (tcb->task_state <= LAST_READY_TO_RUN_STATE)); /* Remove the tcb task from the ready-to-run list. If we * are blocking the task at the head of the task list (the * most likely case), then a context switch to the next * ready-to-run task is needed. In this case, it should * also be true that rtcb == tcb. */ switch_needed = sched_removereadytorun(tcb); /* Add the task to the specified blocked task list */ sched_addblocked(tcb, (tstate_t)task_state); /* If there are any pending tasks, then add them to the ready-to-run * task list now */ if (g_pendingtasks.head) { switch_needed |= sched_mergepending(); } /* Now, perform the context switch if one is needed */ if (switch_needed) { /* Update scheduler parameters */ sched_suspend_scheduler(rtcb); /* Are we in an interrupt handler? */ if (g_current_regs) { /* Yes, then we have to do things differently. * Just copy the g_current_regs into the OLD rtcb. */ up_copystate(rtcb->xcp.regs, g_current_regs); /* Restore the exception context of the rtcb at the (new) head * of the ready-to-run task list. */ rtcb = this_task(); /* Reset scheduler parameters */ sched_resume_scheduler(rtcb); /* Then switch contexts. Any necessary address environment * changes will be made when the interrupt returns. */ g_current_regs = rtcb->xcp.regs; } /* Copy the user C context into the TCB at the (old) head of the * ready-to-run Task list. if up_saveusercontext returns a non-zero * value, then this is really the previously running task restarting! */ else if (!up_saveusercontext(rtcb->xcp.regs)) { /* Restore the exception context of the rtcb at the (new) head * of the ready-to-run task list. */ rtcb = this_task(); #ifdef CONFIG_ARCH_ADDRENV /* Make sure that the address environment for the previously * running task is closed down gracefully (data caches dump, * MMU flushed) and set up the address environment for the new * thread at the head of the ready-to-run list. */ (void)group_addrenv(rtcb); #endif /* Reset scheduler parameters */ sched_resume_scheduler(rtcb); /* Then switch contexts */ up_fullcontextrestore(rtcb->xcp.regs); } } }
void up_block_task(struct tcb_s *tcb, tstate_t task_state) { struct tcb_s *rtcb = this_task(); bool switch_needed; /* Verify that the context switch can be performed */ ASSERT((tcb->task_state >= FIRST_READY_TO_RUN_STATE) && (tcb->task_state <= LAST_READY_TO_RUN_STATE)); /* Remove the tcb task from the ready-to-run list. If we * are blocking the task at the head of the task list (the * most likely case), then a context switch to the next * ready-to-run task is needed. In this case, it should * also be true that rtcb == tcb. */ switch_needed = sched_removereadytorun(tcb); /* Add the task to the specified blocked task list */ sched_addblocked(tcb, (tstate_t)task_state); /* If there are any pending tasks, then add them to the ready-to-run * task list now */ if (g_pendingtasks.head) { switch_needed |= sched_mergepending(); } /* Now, perform the context switch if one is needed */ if (switch_needed) { /* Update scheduler parameters */ sched_suspend_scheduler(rtcb); /* Are we in an interrupt handler? */ if (current_regs) { /* Yes, then we have to do things differently. * Just copy the current_regs into the OLD rtcb. */ up_savestate(rtcb->xcp.regs); /* Restore the exception context of the rtcb at the (new) head * of the ready-to-run task list. */ rtcb = this_task(); /* Reset scheduler parameters */ sched_resume_scheduler(rtcb); /* Then switch contexts. Any new address environment needed by * the new thread will be instantiated before the return from * interrupt. */ up_restorestate(rtcb->xcp.regs); } /* No, then we will need to perform the user context switch */ else { /* Get the context of the task at the head of the ready to * run list. */ struct tcb_s *nexttcb = this_task(); #ifdef CONFIG_ARCH_ADDRENV /* Make sure that the address environment for the previously * running task is closed down gracefully (data caches dump, * MMU flushed) and set up the address environment for the new * thread at the head of the ready-to-run list. */ (void)group_addrenv(nexttcb); #endif /* Reset scheduler parameters */ sched_resume_scheduler(nexttcb); /* Then switch contexts */ up_switchcontext(rtcb->xcp.regs, nexttcb->xcp.regs); /* up_switchcontext forces a context switch to the task at the * head of the ready-to-run list. It does not 'return' in the * normal sense. When it does return, it is because the blocked * task is again ready to run and has execution priority. */ } } }
void up_block_task(_TCB *tcb, tstate_t task_state) { /* Verify that the context switch can be performed */ if ((tcb->task_state < FIRST_READY_TO_RUN_STATE) || (tcb->task_state > LAST_READY_TO_RUN_STATE)) { PANIC(OSERR_BADBLOCKSTATE); } else { _TCB *rtcb = (_TCB*)g_readytorun.head; bool switch_needed; /* Remove the tcb task from the ready-to-run list. If we * are blocking the task at the head of the task list (the * most likely case), then a context switch to the next * ready-to-run task is needed. In this case, it should * also be true that rtcb == tcb. */ switch_needed = sched_removereadytorun(tcb); /* Add the task to the specified blocked task list */ sched_addblocked(tcb, (tstate_t)task_state); /* If there are any pending tasks, then add them to the g_readytorun * task list now */ if (g_pendingtasks.head) { switch_needed |= sched_mergepending(); } /* Now, perform the context switch if one is needed */ if (switch_needed) { /* Are we in an interrupt handler? */ if (current_regs) { /* Yes, then we have to do things differently. * Just copy the current_regs into the OLD rtcb. */ up_savestate(rtcb->xcp.regs); /* Restore the exception context of the rtcb at the (new) head * of the g_readytorun task list. */ rtcb = (_TCB*)g_readytorun.head; /* Then switch contexts */ up_restorestate(rtcb->xcp.regs); } /* No, then we will need to perform the user context switch */ else { /* Switch context to the context of the task at the head of the * ready to run list. */ _TCB *nexttcb = (_TCB*)g_readytorun.head; up_switchcontext(rtcb->xcp.regs, nexttcb->xcp.regs); /* up_switchcontext forces a context switch to the task at the * head of the ready-to-run list. It does not 'return' in the * normal sense. When it does return, it is because the blocked * task is again ready to run and has execution priority. */ } } } }
void up_block_task(_TCB *tcb, tstate_t task_state) { /* Verify that the context switch can be performed */ if ((tcb->task_state < FIRST_READY_TO_RUN_STATE) || (tcb->task_state > LAST_READY_TO_RUN_STATE)) { PANIC(OSERR_BADBLOCKSTATE); } else { _TCB *rtcb = (_TCB*)g_readytorun.head; bool switch_needed; /* Remove the tcb task from the ready-to-run list. If we * are blocking the task at the head of the task list (the * most likely case), then a context switch to the next * ready-to-run task is needed. In this case, it should * also be true that rtcb == tcb. */ switch_needed = sched_removereadytorun(tcb); /* Add the task to the specified blocked task list */ sched_addblocked(tcb, (tstate_t)task_state); /* If there are any pending tasks, then add them to the g_readytorun * task list now */ if (g_pendingtasks.head) { switch_needed |= sched_mergepending(); } /* Now, perform the context switch if one is needed */ if (switch_needed) { /* Are we in an interrupt handler? */ if (current_regs) { /* Yes, then we have to do things differently. * Just copy the current_regs into the OLD rtcb. */ up_savestate(rtcb->xcp.regs); /* Restore the exception context of the rtcb at the (new) head * of the g_readytorun task list. */ rtcb = (_TCB*)g_readytorun.head; /* Then switch contexts */ up_restorestate(rtcb->xcp.regs); } /* Copy the user C context into the TCB at the (old) head of the * g_readytorun Task list. if up_saveusercontext returns a non-zero * value, then this is really the previously running task restarting! */ else if (!up_saveusercontext(rtcb->xcp.regs)) { /* Restore the exception context of the rtcb at the (new) head * of the g_readytorun task list. */ rtcb = (_TCB*)g_readytorun.head; /* Then switch contexts */ up_fullcontextrestore(rtcb->xcp.regs); } } } }
/************************************************************************************ * Name: taskmgr_ioctl * * Description: The ioctl method for task management. * ************************************************************************************/ static int taskmgr_ioctl(FAR struct file *filep, int cmd, unsigned long arg) { int ret = -EINVAL; struct tcb_s *tcb; tmvdbg("cmd: %d arg: %ld\n", cmd, arg); /* Handle built-in ioctl commands */ switch (cmd) { case TMIOC_START: ret = taskmgr_task_init((int)arg); if (ret != OK) { tmdbg("Fail to init new task\n"); } break; case TMIOC_PAUSE: tcb = sched_gettcb((int)arg); if (tcb == NULL) { tmdbg("Invalid pid\n"); return ERROR; } if (tcb->task_state == TSTATE_WAIT_SIG && tcb->waitdog != NULL) { /* tcb is waiting another signal, e.g. sleep */ wd_cancel(tcb->waitdog); } else if (tcb->task_state == TSTATE_WAIT_SEM) { tcb->waitsem = NULL; sched_removeblocked(tcb); sched_addblocked(tcb, TSTATE_WAIT_SIG); } ret = OK; break; case TMIOC_UNICAST: tcb = sched_gettcb((int)arg); if (tcb == NULL) { tmdbg("Invalid pid\n"); return ERROR; } ret = (int)sig_is_handler_registered(tcb, SIGTM_UNICAST); if ((bool)ret != true) { tmdbg("handler is not registered for unicast\n"); ret = ERROR; } else { ret = OK; } break; case TMIOC_RESTART: break; case TMIOC_BROADCAST: tcb = sched_gettcb((int)arg); if (tcb == NULL) { tmdbg("Invalid pid\n"); return ERROR; } ret = (int)sig_is_handler_registered(tcb, SIGTM_BROADCAST); if ((bool)ret != true) { tmdbg("handler is not registered for broadcast\n"); ret = ERROR; } else { ret = OK; } break; case TMIOC_CHECK_ALIVE: tcb = sched_gettcb((int)arg); if (tcb == NULL) { tmdbg("Invalid pid\n"); return ERROR; } ret = OK; break; default: tmdbg("Unrecognized cmd: %d arg: %ld\n", cmd, arg); break; } return ret; }
/**************************************************************************** * Name: up_block_task * * Description: * The currently executing task at the head of * the ready to run list must be stopped. Save its context * and move it to the inactive list specified by task_state. * * This function is called only from the NuttX scheduling * logic. Interrupts will always be disabled when this * function is called. * * Inputs: * tcb: Refers to a task in the ready-to-run list (normally * the task at the head of the list). It most be * stopped, its context saved and moved into one of the * waiting task lists. It it was the task at the head * of the ready-to-run list, then a context to the new * ready to run task must be performed. * task_state: Specifies which waiting task list should be * hold the blocked task TCB. * ****************************************************************************/ void up_block_task(struct tcb_s *tcb, tstate_t task_state) { /* Verify that the context switch can be performed */ if ((tcb->task_state < FIRST_READY_TO_RUN_STATE) || (tcb->task_state > LAST_READY_TO_RUN_STATE)) { warn("%s: task sched error\n", __func__); return; } else { struct tcb_s *rtcb = current_task; bool switch_needed; /* Remove the tcb task from the ready-to-run list. If we * are blocking the task at the head of the task list (the * most likely case), then a context switch to the next * ready-to-run task is needed. In this case, it should * also be true that rtcb == tcb. */ switch_needed = sched_removereadytorun(tcb); /* Add the task to the specified blocked task list */ sched_addblocked(tcb, (tstate_t)task_state); /* Now, perform the context switch if one is needed */ if (switch_needed) { struct tcb_s *nexttcb; /* Update scheduler parameters */ sched_suspend_scheduler(rtcb); /* this part should not be executed in interrupt context */ if (up_interrupt_context()) { panic("%s: %d\n", __func__, __LINE__); } /* If there are any pending tasks, then add them to the ready-to-run * task list now. It should be the up_realease_pending() called from * sched_unlock() to do this for disable preemption. But it block * itself, so it's OK. */ if (g_pendingtasks.head) { warn("Disable preemption failed for task block itself\n"); sched_mergepending(); } nexttcb = this_task(); #ifdef CONFIG_ARCH_ADDRENV /* Make sure that the address environment for the previously * running task is closed down gracefully (data caches dump, * MMU flushed) and set up the address environment for the new * thread at the head of the ready-to-run list. */ (void)group_addrenv(nexttcb); #endif /* Reset scheduler parameters */ sched_resume_scheduler(nexttcb); /* context switch */ up_switchcontext(rtcb, nexttcb); } } }
void up_block_task(struct tcb_s *tcb, tstate_t task_state) { struct tcb_s *rtcb = (struct tcb_s*)g_readytorun.head; bool switch_needed; /* Verify that the context switch can be performed */ ASSERT((tcb->task_state >= FIRST_READY_TO_RUN_STATE) && (tcb->task_state <= LAST_READY_TO_RUN_STATE)); sdbg("Blocking TCB=%p\n", tcb); /* Remove the tcb task from the ready-to-run list. If we * are blocking the task at the head of the task list (the * most likely case), then a context switch to the next * ready-to-run task is needed. In this case, it should * also be true that rtcb == tcb. */ switch_needed = sched_removereadytorun(tcb); /* Add the task to the specified blocked task list */ sched_addblocked(tcb, (tstate_t)task_state); /* If there are any pending tasks, then add them to the g_readytorun * task list now */ if (g_pendingtasks.head) { switch_needed |= sched_mergepending(); } /* Now, perform the context switch if one is needed */ if (switch_needed) { /* Update scheduler parameters */ sched_suspend_scheduler(rtcb); /* Copy the exception context into the TCB at the (old) head of the * g_readytorun Task list. if up_setjmp returns a non-zero * value, then this is really the previously running task restarting! */ if (!up_setjmp(rtcb->xcp.regs)) { /* Restore the exception context of the rtcb at the (new) head * of the g_readytorun task list. */ rtcb = (struct tcb_s*)g_readytorun.head; sdbg("New Active Task TCB=%p\n", rtcb); /* The way that we handle signals in the simulation is kind of * a kludge. This would be unsafe in a truly multi-threaded, interrupt * driven environment. */ if (rtcb->xcp.sigdeliver) { sdbg("Delivering signals TCB=%p\n", rtcb); ((sig_deliver_t)rtcb->xcp.sigdeliver)(rtcb); rtcb->xcp.sigdeliver = NULL; } /* Reset scheduler parameters */ sched_resume_scheduler(rtcb); /* Then switch contexts */ up_longjmp(rtcb->xcp.regs, 1); } } }