程序后台运行

这是一段NuttX RTOS的代码,用于完成接收到终端中收到的指令并执行的过程, 请帮我解读一下他是什么含义

if (vtbl->np.np_bg == true)
    {
      act.sa_handler = SIG_DFL;
      act.sa_flags = SA_NOCLDWAIT;
      sigemptyset(&act.sa_mask);

      sigaction(SIGCHLD, &act, &old);
    }

这段代码是在 NuttX 实时操作系统(RTOS)中使用的,用于处理子进程结束时的信号。

act.sa_handler = SIG_DFL; 这行代码将 act 结构体中的 sa_handler 成员设置为 SIG_DFLSIG_DFL 是一个默认的信号处理程序,用于处理接收到的信号。

接下来,act.sa_flags = SA_NOCLDWAIT; 这行代码将 act 结构体中的 sa_flags 成员设置为 SA_NOCLDWAITSA_NOCLDWAIT 是一个标志,当设置了这个标志,如果子进程结束,它不会变成僵尸进程。

然后,sigemptyset(&act.sa_mask); 这行代码将 act 结构体中的 sa_mask 成员设置为一个空的信号集。sigemptyset 函数用于清空一个信号集。

最后,sigaction(SIGCHLD, &act, &old); 这行代码调用 sigaction 函数,用于改变 SIGCHLD 信号的处理方式。SIGCHLD 信号在一个进程结束时被发送给其父进程。这行代码将 SIGCHLD 信号的处理方式设置为 act 结构体中定义的方式。

总的来说,这段代码的作用是,当 vtbl->np.np_bgtrue 时,改变 SIGCHLD 信号的处理方式,使得子进程结束时不会变成僵尸进程。这对于在后台运行的进程非常有用,可以避免产生僵尸进程。这是一种常见的在 Unix 和类 Unix 系统(如 Linux)中处理子进程结束的方式。在 NuttX 这样的实时操作系统中,也可以使用类似的方式来处理子进程结束。这段代码是处理子进程结束信号的一部分,可能是一个更大的函数或程序的一部分。具体的使用方式可能会根据实际的程序逻辑和需求有所不同。


nuttx中是没有僵尸进程的概念的,所以在nuttx中是如何处理的呢?另外你说的子进程的结束信号是什么意思?是说每个子进程结束都会给他的父进程发送一个信号吗?

在 NuttX 实时操作系统中,由于其设计和实现的特性,确实没有僵尸进程的概念。当一个子进程结束时,其父进程不需要显式地回收其资源,这一点与 Unix 和类 Unix 系统(如 Linux)有所不同。

关于子进程结束信号,这是 Unix 和类 Unix 系统中的一个概念。在这些系统中,当一个子进程结束时,会向其父进程发送一个 SIGCHLD 信号。父进程可以选择忽略这个信号,或者提供一个信号处理函数来响应这个信号。通常,父进程会在这个信号处理函数中调用 wait 或 waitpid 函数来获取子进程的退出状态,并回收其资源,从而防止子进程成为僵尸进程。

然而,在 NuttX 中,由于没有僵尸进程的概念,因此也就没有必要提供类似的机制来处理子进程的结束。当一个 NuttX 的子任务(或者说子进程)结束时,其资源会被自动回收。

总的来说,NuttX 和 Unix 系统在处理子进程结束方面有一些不同。这主要是由于它们的设计和实现有所不同,以适应不同的使用场景和需求。


nuttx在nsh shell中执行程序,在执行的程序后面加&和不加&,nuttx的执行逻辑是什么样的,为什么不加&他会把终端给占住了,敲什么命令都没有反应了,可以结合nuttx代码给我说一下它的原理。

在 NuttX 的 NSH shell 中,执行程序时是否在命令后面添加 & 符号,会影响程序的运行方式。

  • 如果你在命令后面添加了 & 符号,那么 NSH shell 会在后台启动这个程序。这意味着,尽管程序仍在运行,但 NSH shell 会立即返回,允许你输入和执行其他命令。这就是所谓的 “后台运行”。

  • 如果你没有在命令后面添加 & 符号,那么 NSH shell 会在前台启动这个程序。这意味着,NSH shell 不会返回,直到程序完成执行。在此期间,你不能输入或执行其他命令。这就是所谓的 “前台运行”。这种行为与 Unix 和类 Unix 系统(如 Linux)中的 shell 是一致的。

至于为什么没有加 & 的命令会占据终端,这是因为在前台模式下,shell 会等待当前运行的进程结束,然后再接收新的用户输入。这就是为什么你在程序运行时无法输入其他命令的原因。

在 NuttX 的源代码中,这种行为是由 NSH 的解析器和调度器实现的。当 NSH 解析器解析到一个命令时,它会创建一个新的任务来执行这个命令。然后,根据命令后面是否有 & 符号,NSH 调度器会决定是在前台还是在后台运行这个任务。


那他是怎么保证程序在前台一直运行的。

在 NuttX 的 NSH shell 中,当你在前台运行一个程序时,shell 会等待该程序完成执行才会接收新的用户输入。这是通过在 shell 内部使用一种称为 “进程阻塞” 或 “等待” 的技术来实现的。

具体来说,当你在 NSH shell 中输入一个命令并按下回车键时,shell 会创建一个新的任务(在 NuttX 中,任务相当于进程)来执行该命令。然后,如果命令是在前台运行的(即,命令后面没有 & 符号),shell 会调用一个特殊的系统调用(如 waitpid() 或类似的函数)来等待新创建的任务完成执行。

这个 waitpid() 系统调用会导致 shell 进程阻塞,即暂停执行,直到新创建的任务完成执行。在 shell 进程阻塞期间,它不会接收或处理任何新的用户输入。只有当新创建的任务完成执行,waitpid() 系统调用返回,shell 进程才会解除阻塞,继续接收和处理新的用户输入。

这就是为什么当你在 NSH shell 中在前台运行一个程序时,你不能输入其他命令的原因。只有等到当前运行的程序完成执行,shell 才会再次接收你的输入。

这种设计使得你可以在 shell 中一次启动一个程序,等待它完成,然后再启动下一个程序。这对于需要交互式地运行多个程序的用户来说是非常有用的。然而,如果你想要同时运行多个程序,你可以在命令后面添加 & 符号,让程序在后台运行,这样 shell 就可以立即返回,接收新的用户输入了。


在父进程创建子进程后,父进程可以通过调用wait()waitpid()函数来等待子进程的结束。这两个函数的工作原理如下:

  1. 当子进程结束时,操作系统会向其父进程发送一个SIGCHLD信号。
  2. 父进程在调用wait()waitpid()函数后会被阻塞。
  3. 当父进程收到SIGCHLD信号后,它会被唤醒,然后去回收已经结束的子进程,防止子进程变成僵尸进程。
  4. 如果父进程没有任何子进程,wait()waitpid()函数会返回错误。

所以,父进程是通过接收到操作系统发送的SIGCHLD信号来知道子进程已经结束的。这个机制解决了父子进程之间的异步通信问题,让父进程可以及时地去回收已经结束的子进程。如果父进程没有调用wait()waitpid()函数来等待子进程的结束,那么结束的子进程就会变成僵尸进程。这是因为子进程的一些资源,如进程描述符、进程状态等,只有在父进程通过wait()waitpid()函数获取了这些信息后,才会被操作系统回收。因此,父进程需要调用wait()waitpid()函数来等待子进程的结束,并回收子进程的资源。这样可以防止资源的浪费,提高系统的效率。

当父进程收到SIGCHLD信号时,它会调用预先定义的信号处理函数来处理这个信号。这个信号处理函数通常会调用wait()waitpid()函数来获取已经结束的子进程的状态信息,并告诉操作系统可以回收这个子进程的资源。

下面是一个简单的例子,展示了如何在信号处理函数中使用waitpid()函数来回收子进程的资源:

#include 
#include 
#include 
#include 
#include 
#include 

void handle_sigchld(int sig) {
  printf("################# aaaaaaaaaaaaaaa\n");
  int saved_errno = errno;
  while (waitpid((pid_t)(-1), 0, WNOHANG) > 0) {
  }
  errno = saved_errno;
}

int main() {
  struct sigaction sa;
  sa.sa_handler = &handle_sigchld;
  sigemptyset(&sa.sa_mask);
  sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
  if (sigaction(SIGCHLD, &sa, 0) == -1) {
    perror(0);
    exit(1);
  }

  pid_t pid = fork();
  if (pid == 0) {
    printf("This is the child process. pid=%d\n", getpid());
    sleep(10); // simulate some work in the child process
    printf("Child process is done. pid=%d\n", getpid());
  } else if (pid > 0) {
    printf("This is the parent process. pid=%d, child pid=%d\n", getpid(), pid);
    while (1) {
      printf("Parent process is working...\n");
      sleep(1);
    }
  } else {
    printf("fork failed!\n");
    return 1;
  }

  return 0;
}

在这个例子中,我们首先定义了一个handle_sigchld()函数,这个函数会在收到SIGCHLD信号时被调用¹。这个函数的作用就是调用waitpid()函数来回收所有已经结束的子进程的资源。然后,我们使用sigaction()函数来设置SIGCHLD信号的处理函数为handle_sigchld()。这样,每当有子进程结束时,handle_sigchld()函数就会被自动调用,从而回收子进程的资源。


wait()waitpid()都是用于父进程等待子进程结束的函数,它们的主要作用是防止子进程成为僵尸进程,防止内存泄漏。

wait()函数的作用是父进程调用,等待子进程退出,回收子进程的资源。它的函数原型如下:

#include
#include
pid_t wait(int*status);

其中,status是一个输出型参数,用于获取子进程的退出状态。如果不关心子进程的退出状态,则可以将status设置为NULL。

waitpid()函数则是wait()函数的增强版,它允许父进程在子进程还没有结束时就返回。它的函数原型如下:

#include
#include
pid_t waitpid(pid_t pid, int *status, int options);

其中,pid参数用于指定欲等待的子进程识别码,status参数用于保存子进程的状态信息,options参数提供了一些额外的选项来控制waitpid()函数的行为。以下是一个waitpid()函数的使用示例:

#include 
#include 
#include 
#include 
#include 

int main() {
    pid_t pid, childpid;
    int status = 0;

    pid = fork();
    if (pid < 0)
        printf("Error occured on forking.\n");
    else if (pid == 0) { //子进程
        sleep(3); //换成30s,然后kill -9 子进程pid
        exit(0);
    } else { //父进程
        //返回后继续执行父进程的代码段
    }

    printf("pid:%d\n",pid); //打印子进程id

    do {
        childpid = waitpid(pid, &status, WNOHANG);
        if (childpid == 0) {
            printf("No child exited,ret = %d\n", childpid);
            sleep(1);
        }
    } while (childpid == 0);

    if (WIFEXITED(status))
        printf("正常退出:%d\n",childpid);
    if(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL)
        printf("被SIGKILL信号结束\n");
}

在这个示例中,父进程通过waitpid()函数等待子进程结束,并通过status参数获取子进程的退出状态。如果子进程还没有结束,waitpid()函数会立即返回0,父进程不会被阻塞。这就是waitpid()函数相比wait()函数的一个优势。当子进程结束时,waitpid()函数会返回子进程的进程ID,并通过WIFEXITED(status)WIFSIGNALED(status)宏来检查子进程是正常退出还是被信号结束。这样,父进程就可以根据子进程的退出状态来进行相应的处理。。

你可能感兴趣的:(我的博客,Linux,NuttX)