进程的控制终端规则详解(打开串口设备文件时的标志位O_NOCTTY的详解、守护进程的概念)

前提

在Linux的串口驱动开发中,通常都是以TTY框架作为其驱动开发框架。

使用 TTY 框架开发串口驱动,可以让串口设备具备标准的终端特性,并能通过 /dev/ttyX 这样的设备文件访问。关于终端的详细解释,请参考我的另一篇博文 https://blog.youkuaiyun.com/wenhao_ir/article/details/145431655

由于咱们的串口设备的驱动是基于TTY框架开发的,那么咱们打开的一个串口设备,同样也能同时作为串口终端来使用。这一点其实就是咱们本博文中要讲的问题的来源。

所以,看下面的内容前,你要谨记,本文下面内容的前提是假设我们的串口驱动是基于TTY的驱动框架开发的。

我的提问

提问:假设有一个ELF程序A,它在串口终端/dev/ttyS0中被打开运行,那么程序A的进程是不是就是以串口终端/dev/ttyS0作为自己的控制终端?

回答:
是的,如果一个 ELF 程序 A 在 串口终端 /dev/ttyS0 中被打开并运行,那么它的进程默认会把 /dev/ttyS0 作为自己的控制终端(Controlling Terminal),除非该进程在运行时主动关闭或更改控制终端。


对提问的回答

1. 进程的控制终端规则

在 Linux 中,进程的控制终端(Controlling Terminal)是指进程组(Process Group)与终端设备之间的绑定关系。通常:

  1. 登录 Shell(如 bash)的控制终端

    • 当你使用 getty 登录 Linux 并进入 Shell(如 /bin/sh/bin/bash),这个 Shell 进程会把 当前终端(TTY) 设为自己的控制终端
    • 如果你是在 /dev/ttyS0 上登录的,Shell 进程的控制终端就是 /dev/ttyS0
  2. 启动程序 A 的控制终端

    • 如果你从 Shell 运行 ELF 程序 A,例如:
      ./A
      
      • A 进程会继承 Shell 的控制终端,即 /dev/ttyS0
      • 这意味着:
        • A 进程可以从 /dev/ttyS0 读取标准输入stdin)。
        • A 进程的标准输出(stdout)和标准错误输出(stderr)都会默认写入 /dev/ttyS0
        • A 进程可能会受到终端信号影响,例如:
          • Ctrl+C 会发送 SIGINT,终止 A
          • Ctrl+Z 会发送 SIGTSTP,暂停 A
  3. 如何验证?

    • 运行 A 后,在另一个 Shell 中执行:
      ps -o pid,pgid,sid,tpgid,tty,comm -p $(pgrep A)
      
      • tty 字段应该显示 ttyS0,表明该进程的控制终端是 /dev/ttyS0

2. 影响进程与进程的控制终端之间的关系与行为的一些方式

(1)后台运行

如果 A 进程在 Shell 中后台运行

./A &

仍然会把 /dev/ttyS0 作为控制终端,但不会直接与用户交互

(2)nohup 运行

如果 A 是通过 nohup 运行的:

nohup ./A &
  • A 仍然继承 /dev/ttyS0 作为控制终端,但:
    • stdoutstderr 默认会被重定向到 nohup.out
    • A 不会收到 SIGHUP 信号(即使终端关闭,进程仍然继续运行)。

(3)setsid() 让进程脱离控制终端成为一个类似于守护进程的存在

如果 A 在代码里调用:

setsid();
  • 这会让 A 脱离控制终端,成为一个新的会话(Session)和进程组(Process Group)的领导者
  • 之后,A 不再属于 /dev/ttyS0,不会受到终端信号(如 SIGHUPSIGINT)的影响。从而使得进程脱离控制终端成为一个类似于守护进程的存在。

如果 setsid() 执行成功:

  • ps 命令的 tty 字段会变成 ?(表示没有控制终端)。

如果 setsid() 失败:

  • 可能 A 本身已经是会话首进程,导致 setsid() 调用失败(返回 -1)。

示例代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
    if (setsid() == -1) {
        perror("setsid failed");
        exit(EXIT_FAILURE);
    }

    printf("Process is now detached from controlling terminal.\n");

    while (1) {
        sleep(1);
    }

    return 0;
}

(4)O_NOCTTY 影响

分两种情况来讲这个问题。

第一种情况:A 进程没有控制终端;
第二种情况:A进程有控制终端。

下面详细分析这两种情况:

第一种情况: A 进程没有控制终端
A 进程没有控制终端时,如果 A 在运行时用下面的语句打开 /dev/ttyS0

fd = open("/dev/ttyS0", O_RDWR);

此时由于没有设置标志位O_NOCTTY,又由于此时A进程没有控制终端,那么此时A 进程会自动把 /dev/ttyS0 设为自己的控制终端。
注意:A进程把/dev/ttyS0设定为自己的控制终端的这种行为与/dev/ttyS0是不是一个已经被打开的串口终端无关,即不管此时/dev/ttyS0是否是一个串口终端,A进程都可以把 /dev/ttyS0 设为自己的控制终端。
我们可以看到,本来嘛,A进程是没有控制终端的,但是因为A程序中打开设备文件/dev/ttyS0的操作,使得它有了控制终端,更加不幸的是,如果/dev/ttyS0已经或后来被打开作为一个串口终端,那么这个串口终端的某些信号会影响到我们的进程A,比如终端关闭时产生的终端挂断信号(SIGHUP)会让进程A也终止运行,这很有可有不是我们希望发生的。
那么如何使得一个没有控制终端的进程A在打开串口设备文件 /dev/ttyS0 时,不被绑定上控制终端呢,那就是设置标志位O_NOCTTY,比如用下面语句打开 /dev/ttyS0

fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY);

当设置了标志位O_NOCTTY,那么A进程则不会把 /dev/ttyS0 设为自己的控制终端。这样,即使 /dev/ttyS0 已经或后来被打开成为一个串口终端,这个串口终端的所有行为也不会影响到我们的进程A了。

第二种情况:A进程有控制终端

当A程序打开设备文件/dev/ttyS0时,由于此时进程A已有控制终端了,所以此时进程A不会把 /dev/ttyS0 设为新的控制终端。即此时此刻,你设不设置标志位O_NOCTTY对进程A的控制终端都没有影响。

3. 结论

(1)默认情况

  • 如果 ELF 程序 A 是在 /dev/ttyS0 终端运行的,那么 A 进程的控制终端就是 /dev/ttyS0
  • 这意味着:
    • A 可以使用 stdin/dev/ttyS0 读取输入。
    • Astdoutstderr 会打印到 /dev/ttyS0
    • A 会受到 /dev/ttyS0 终端的控制信号(如 Ctrl+C)。

(2)如何避免进程绑定控制终端

  • nohup ./A &:防止 SIGHUP 影响,但仍然绑定 /dev/ttyS0
  • setsid():完全脱离控制终端,成为无终端的守护进程。
  • open("/dev/ttyS0", O_RDWR | O_NOCTTY);:即使 A 打开 /dev/ttyS0,也不会变成控制终端。

(4)如何查看进程的控制终端

可以使用:

ps -o pid,pgid,sid,tpgid,tty,comm -p $(pgrep A)

示例输出:

  PID  PGID   SID  TPGID  TTY    COMMAND
 1234  1234  1234   1234  ttyS0  A

如果 TTY?,说明 A 没有控制终端。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值