linux下的session leader及controlling terminal

最近在调试嵌入式下设备启动基于busybox的init程序所执行的rc.sysinit脚本时,发现该脚本执行的后台脚本,会因为没有关联到串口设备(通过ps的TTY一列可以确认) ,导致我们做了tty设备检测的程序无法顺利执行,借此机会,查阅了busybox的init代码,并对linux的session group和controlling terminal有了一定的理解。

在libc的info文档中Job Control一节,有如下几个重要的定义:

  • process group,即进程组,用管道符创建的一堆命令就行成了一个进程组。
  • session,即会话,通常从login shell登陆后的所有进程都属于一个session,即session由login shell创建,注意libc的文档还是从传统的语义来讲的,对于现代的大多数桌面环境而言(DE,如KDE/GNOME等),一个图形会话,往往会通过setsid函数起大量的session),一个session下可以管理多个process group,一个进程可以在同一个session下的不同process group中迁移,但将一个进程移到另外的会话中则只能通过setsid这个函数来创建新的会话实现。
  • session leader,即会话leader,创建会话的进程称为会话leader,从上面的描述可知,login shell一般为会话leader,首次调用setsid的进程也将成为session leader。
  • controlling terminal,即控制终端,进程的重要属性之一,由fork创建的子进程继续父进程的controlling terminal,而从setsid的libc文档中可以看出(或者用man 3 setsid查看),setsid调用后,进程将做为新的会话的会话leader,并且丢失controlling terminal属性,而其后该会话leader打开的首个tty设备,将成为该会话的controlling terminal(见附2说明);shell通常只会将controlling terminal这个属性给予一个进程组,以便由这个进程组通过终端设备获取输入或者输出信息,这组进程将称为前台任务,而未获得输入或输出等权限的进程组,在尝试向controlling terminal读取或写入数据时,将收到SIGTTIN或SIGTTOU信号,默认情况下这个信号将中止相关程序的执行。这点是合理的,因为如果不做此限制,后台程序很可能扰乱终端输出或者输入的处理。

经过上述讲述,以代码形式给出上述概念的演示。

#include "stdlib.h"
#include "errno.h"
#include "unistd.h"
#include "fcntl.h"

int main()
{
	int err, fd;
	pid_t pid;
	char *pts_name;

    pid = fork();
    if (pid != 0) {
        exit(0); // 主进程退出。
	}

    pid = setsid(); // 在子进程中创建新的会话

    // 打印从父进程继续而来的tty,注意,该tty已经不是我们的controlling terminal
	printf("before we prepare the new stdio, the child inherit its parent's stdio %s\n", ttyname(0));

    // 如下一段代码用于创建一个pseudo-terminal,将作为tty设备被新的会话leader打开,成为该会话的controlling terminal
    fd = getpt();
    pts_name = ptsname(fd);
    printf("allocated a new pts is %s\n", pts_name); 
    grantpt(fd);
    unlockpt(fd);

    // 首次open的tty设备将成为controlling terminal
	fd = open(ptsname(fd), O_RDWR);

    // 将该fd做为标准输入,在本例中意义不大
	close(STDIN_FILENO);
	dup2(fd, STDIN_FILENO);

    // 停留2分,以便我们可以通过ps检验是否获得了新的session及controlling terminal
	sleep(120);
}

编译并执行上述程序后,执行后,立即用如下命令,可以查看到新进程确实拥有了新的controlling terminal了。

ps  -u fortitude -o pid,args,tt,sess,ppid

PID COMMAND                     TT        SESS  PPID
8086 ./sessionleader             pts/5     8086     1

参考文档:

  1. libc info,可在shell下info libc,或在emacs下info libc查看
  2. http://uw714doc.sco.com/en/SDK_sysprog/_The_Controlling-Terminal_and_Pr.htmlWhen a session-leader without a controlling-terminal opens a terminal-device-file and the flag O_NOCTTY is clear on open, that terminal becomes the controlling-terminal assigned to the session-leader if the terminal is not already assigned to some session (see open(2)). When any process other than a session-leader opens a terminal-device-file, or the flag O_NOCTTY is set on open, that terminal does not become the controlling-terminal assigned to the calling-process.

shell中通过set builtin操作positional parameter

最近在调试ubuntu 14.04上的openvswitch服务时,发现openvswitch在upstart里的脚本实现频繁用到了set这样一个shell builtin,代码写的不是很好理解,后来经过查询shell相关文档,终于理解了通过set这个builtin来操作位置变量确实是一个很不错的办法。

bash的info文档中专门有一章节来介绍The Set Builtin,并且由于builtin is so complicated that it deserves its own section,但从本文的角度来看,我们认为set就是用来操作位置变量的,以下用一个简单的示例,就可以很容易的给出set的妙用了。

set -- # --这个特殊的用法来清空位置变量,即用$@来访问位置变量将得到空,除了--,set还有其他很多用法,具体可以查看shell的info文档
set "$@" ls # 用ls来初始化位置变量,ls将被赋值给$1
set "$@" -l # 将$@展开后,与-l一起重新赋值给位置变量,使得ls被赋值给$1,-l给赋值给$@
$@ # 展开位置变量,并执行,等同于执行ls -l命令

从上面的代码示例可以看出,set可以将其后的一串参数赋值给位置变量,从而方便对命令行扩充,支持更好的扩展性,并且由于@这个位置变量全集的通用性,使得在脚本嵌套时,可以方便在被执行的脚本中,对原调用脚本传过来的参数进行进一步的编辑,从而实现更为复杂的应用,比如上述openvswitch的upstart脚本,就在这点上大做了文章。

从这点感觉,bash设计的还是很精妙的,工作中大多数采用C编程,对于shell脚本的编程实践较少,后续需要继续掌握这些细节和强大之处:)