1.用户登录到linux时,系统为用户启动的第一个进程就是bash shell,每个登录的用户都有一个bash shell进程为其服务,且每个bash shell的配置和运行情况有可能不同。这个shell将会成为用户的最高的父进程,以后在此进程下启动的所有的进程都是shell的子进程。环境变量是由父进程传承给子进程的,所以当用户在shell下export的环境变量始终可见于所有的在此shell下运行的程序中,但对其它用户的shell来说是不可见的。当然每个shell需要一个terminal跟用户互动,为此Linux提供了多种终端机制,其中在图形界面是以master和slave的pts虚拟了terminal,这样terminal跟程序无关了,任何想跟用户交互的程序都能用虚拟终端来实现。大概如下: (1) shell进程用于master fd <----------------->slave fd terminal跟用户交互。 (2) A机器上的 ssd进程拥有master fd <--------------->A机器上的slave fd terminal用户交互, | | A和B通过ssh的端口交流 | B机器上的ssh client拥有master fd <------------------>B机器上的slave fd terminal用户交互 (3) 自己可以实现类似于shell的进程,实现自己的命令行和相应的控制。
而在非图形模式下是由tty来实现terminal的,tty其实就是console,就是机器和人交互的terminal。在fedora上tty1就是图形界面,而图形界面上的每个terminal终端就是一个pts。 当我们敲打who命令时: root tty2 2013-07-31 03:20 root tty4 2013-07-31 03:22 liuj tty1 2013-07-30 21:17 (:0) liuj pts/0 2013-07-30 21:22 (:0.0) liuj pts/1 2013-07-30 21:24 (:0.0) 在本机上有三个终端接入,tty1就是图形界限,此图形界面下有两个terminal窗口,另外有两个非图形接入的root用户tty2和tty3. 一下是网上的搜集到的一些内容,参考: A tty is a native terminal device, the backend is either hardware or kernel emulated. A pty (pseudo terminal device) is a terminal device which is emulated by an other program (example: xterm , screen , or ssh are such programs). A pts is the slave part of a pty. (More info can be found in man pty .) A pty is created by a process through posix_openpt() (which usually opens the special device /dev/ptmx ), and is constituted by a pair of bidirectional character devices: The master part, which is the file descriptor obtained by this process through this call, is used to emulate a terminal. After some initialization, the second part can be unlocked with unlockpt() , and the master is used to receive or send characters to this second part (slave). The slave part, which is anchored in the filesystem as /dev/pts/x (the real name can be obtained by the master throughptsname() ) behaves like a native terminal device (/dev/ttyx ). In most cases, a shell is started that uses it as a controlling terminal. 从这可以知道每此用户登录shell都会创建/dev/pts/*来跟用户交互,而shell自己控制/dev/ptmx. 默认情况这个shell下运行程序的默认输入是键盘输入,默认输出是显示器。 通过重定向 >> < >来改变输入和输出,要注意的是重定向一定要到一个文件fd或设备fd,而不能是程序。 Linux的很多cmd当其有带有文件名时,程序自动更改自己的输入为这个文件,等于cat < file。这个就是linux类程序的属性。而pipe管道就是把输出指定到一个程序,比重定向高级了一下,其实管道就是中间多加了两次重定向操作一样。 shell中单词的界线默认有两个一个 空格 另一个 点.符号。记住这个很重要的。 子shell继承父shell的环境变量时只是把值靠过来的,所以在子shell中修改环境变量,并不会影响父进程的环境变量。比如在子shell中修改PATH,并不会影响其父子的PATH变量值。 但是函数跟shell是共享一个参数的,在函数中修改的参数值,在shell中继续使用,不会回复到原来的值。
telnet client窗口关闭后服务器端前台任务如何退出 2012-03-28 22:21:34| 分类: Busybox|字号 订阅 一、telnet客户端窗口粗暴关闭 一般很多共享式系统都会启动telnet服务,特别是在嵌入式系统中,通常除了串口就是telnet来和单板交互了。典型的场景是一个用户可能通过后台的windows或者linux系统的telnet客户端来telnet连接到服务器上,然后执行操作。在理想情况下,这是一个友好而和谐的沟通方式,但是在工程中往往会出现一些异常路径,而对于这些异常路径的行为我们不能用依据"implementation defined"或者说"不确定"来描述,而需要对这种即时是不确定的行为也要描述一下不确定发生在哪里。 通常的情况是同一个用户可能打开多个telnet客户端来连接到同一个服务器上,然后在每个窗口做不同的操作,当操作结束之后,用户会逐个关闭这些窗口,即使是单独telnet会话,很多人也不会输入exit来友好的结束这次会话,而是直接关闭掉telnet客户端窗口,此时我们就需要知道,此时回话中正在运行的程序会如何退出,因为应用中可能会要求在任务退出的时候来执行一些特殊的清理工作。 二、busybox telnetd运行框架和原理 由于busybox的源代码比较精简,但是功能齐全,所以通过这个来分析是一个比较好的入口。对于telnetd来说,它就是在自己的23端口侦听(这个值可以通过-p选项配置,默认23),这个是telnetd所说的侦听实现 xlisten(master_fd, 1); 每当有请求到达这个端口的时候,telnetd都会派生一个新的会话(make_new_session),这个会话需要两个文件,一个是accept返回的socket,这个套接口专门负责为这次会话通讯,然后打开伪终端的主控文件“/dev/ptmx”,生成一对伪终端,终端的一侧为telnetd打开,另一侧为新派生的会话验证程序(默认为/bin/login,可以通过-l选项指定)的标准输入,而login负责用户身份验证及命令解析器(sh)的派生和执行(我们通过ps是看不到login的,那是因为login在密码验证通过之后直接执行exec,使用sh替换了自己)。而telnetd的主体就是在自己协调的这些主控侦听端口、会话端口、伪终端之间进行select,唤醒之后在 <套接口,伪终端> 之间中转。 三、telnet客户端tcp 套接口发送fin报文 在之前的一篇博客中说过,当进程退出时,内核会代劳执行进程打开的所有文件的close方法。对于TCP套接口,它的close接口中包含了FIN报文的发送(LInux内核中通过tcp_send_fin发送)。当通讯的另一方(这里就是telentd所select的一个通讯套接口)接受到这个FIN报文之后,内核的处理函数会经过tcp_fin函数,这个函数中有一个非常贴心的唤醒操作: \linux-2.6.21\net\ipv4\tcp_input.c sk->sk_shutdown |= RCV_SHUTDOWN; sock_set_flag(sk, SOCK_DONE); …… if (!sock_flag(sk, SOCK_DEAD)) { sk->sk_state_change(sk);
/* Do not send POLL_HUP for half duplex close. */ if (sk->sk_shutdown == SHUTDOWN_MASK || sk->sk_state == TCP_CLOSE) sk_wake_async(sk, 1, POLL_HUP); else sk_wake_async(sk, 1, POLL_IN); } 当telnetd被从select中唤醒之后,它欢欢喜喜的去这个套接口中去读取数据,但是在read-->>>……--->>tcp_recvmsg函数中会判断套接口关闭 if (sock_flag(sk, SOCK_DONE)) break; 此时telnetd从套接口中读取时返回值为零。然后看一下busybox中对于这个read返回值为零的行为如何反应: count = safe_read(ts->sockfd_read, TS_BUF1 + ts->rdidx1, count); if (count <= 0) { if (count < 0 && errno == EAGAIN) goto skip3; goto kill_session; } …… kill_session: free_session(ts); 而free_session的功能非常简单,对于我们关心的操作只有下面两个,此处并没有杀死子进程的动作 close(ts->ptyfd); close(ts->sockfd_read); 四、伪终端对于关闭的响应 tty_release--->>>release_dev--->>>tty->driver->close(tty, filp)--->>>pty_close--->>>tty_vhangup--->>do_tty_hangup if (tty->session) { do_each_pid_task(tty->session, PIDTYPE_SID, p) { spin_lock_irq(&p->sighand->siglock); if (p->signal->tty == tty) p->signal->tty = NULL; if (!p->signal->leader) { spin_unlock_irq(&p->sighand->siglock); continue; } __group_send_sig_info(SIGHUP, SEND_SIG_PRIV, p);执行到这里,这个tty对应的会话首领将有幸收到一个SIGHUP信号。 __group_send_sig_info(SIGCONT, SEND_SIG_PRIV, p); put_pid(p->signal->tty_old_pgrp); /* A noop */ if (tty->pgrp) p->signal->tty_old_pgrp = get_pid(tty->pgrp);注意这个tty_old_pgrp将会在接下来的操作中使用。 spin_unlock_irq(&p->sighand->siglock); } while_each_pid_task(tty->session, PIDTYPE_SID, p); } 但是这里只是给会话首领发送SIGHUP,这里的首领一般是telentd-->>login--->>sh,也就是命令解释器,而正在sh前台中运行的任务并没有这个机会,所以这里并不是sh前台任务退出的原因。但是这个信号将会导致sh退出还是没有问题的,因为sh一般是不会注册这个信号的处理函数的,而且即使注册了,它的信号处理函数也应该会退出。 五、sh如何关闭前台任务 当sh由于退出时,内核流程为 do_exit--->>disassociate_ctty struct pid *old_pgrp; spin_lock_irq(¤t->sighand->siglock); old_pgrp = current->signal->tty_old_pgrp; current->signal->tty_old_pgrp = NULL; spin_unlock_irq(¤t->sighand->siglock); if (old_pgrp) { kill_pgrp(old_pgrp, SIGHUP, on_exit); kill_pgrp(old_pgrp, SIGCONT, on_exit); put_pid(old_pgrp); } mutex_unlock(&tty_mutex); unlock_kernel(); return; } 当sh退出时,内核会通过这个机制来给它前台运行的任务发送一个SIGHUP,从而将前台任务结束。 六、引申问题 1、如果前台任务系统对于这种粗暴关闭telnent客户端的行为也进行特殊清理的话,就需要注册对SIGHUP的处理函数,在其中做处理。反过来说,如果希望在telnet客户端关闭之后还继续运行,那就需要注册这个信号处理函数,但是不退出,或者直接忽略这个信号。 2、telent会话中后台任务在telent客户端关闭之后依然存在,不受影响。
|