注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Tsecer的回音岛

Tsecer的博客

 
 
 

日志

 
 

交互shell设置为/dev/console之后提示job control turned off(上)  

2012-02-08 23:18:36|  分类: Busybox |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
一、busybox交互shell设置
这个是一个测试busybox功能的实现,主要就是配置了/etc/inittab文件中有一项,其内容为
/dev/console::respawn:/bin/ash
之类的一个配置项,然后提示了一个错误,所谓“提示无意、听者有心”,我也就注意到了这个提示,所以就看了一下busybox及内核的代码,想说明个大概是啥意思,可能也不一定对,但是慢慢积累,总是要一个过程。这个东西我以前的一篇日志中已经讨论过,现在感觉还是可以再看一下。
二、程序的标准文件描述符从哪里来
其实这个就又回到了最为原始的问题,那就是程序会假设自己的三个描述符(标准输入、标准输出、标准错误)都是可以信赖的,作为用户态第一个启动的、和内核交互的init程序,它的标准输入和输出是从哪里来的,如何确定呢?这里直奔主题了,为什么直接提到init?是因为如果init的确定了之后,所有派生的子进程直接集成就可以了,这就是一个责任推诿的过程,最后总得有个人来完成这个工作,这里来完成这个责任链的用户态终点就落在了init身上。事实上init也很无辜,它也是假设自己的三个标准描述符始终是可用的,所以问题转入内核,看一下内核是如何给init设置描述符的。
linux-2.6.21\init\main.c
static int noinline init_post(void)
{
……
    if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
        printk(KERN_WARNING "Warning: unable to open an initial console.\n");

    (void) sys_dup(0);
    (void) sys_dup(0);
}
这里是打开了文件系统中指定的console设备,把这个文件描述的设备作为init的文件描述符,这样实现的优点就是相同的内容,可以个根据文件系统的不同而订制不同的文件描述符,也就是可配置性强。
看一下我的fedoracore中该文件的描述
[tsecer@Harry root]$ ls -l /dev/console
crw-------. 1 root root 5, 1 2011-12-24 17:57 /dev/console
[tsecer@Harry root]$
遗憾的是,这种设备并不是一个实体设备,同样是一个虚拟设备,也就是它指向了一些东西,但是从这个设备本身无法确定是什么东西,和大家常见的“有关部门”是一个性质的。内核中对于这个设备是有特殊处理的,位于
linux-2.6.21\drivers\char\tty_io.c
static int tty_open(struct inode * inode, struct file * filp)
    if (device == MKDEV(TTYAUX_MAJOR,1)) {////#define TTYAUX_MAJOR        5
        driver = console_device(&index);
        if (driver) {
            /* Don't let /dev/console block */
            filp->f_flags |= O_NONBLOCK;
            noctty = 1;
            goto got_driver;
        }
        mutex_unlock(&tty_mutex);
        return -ENODEV;
    }
为什么说它不确定,因为它是通过console_device函数来获得系统中可用的一个tty设备驱动,这个具体值是多少,就要根据内核配置了多少个可以做为控制台的设备,也就是console_drivers链表中第一个device函数能够返回驱动的那个设备。当然这里可能太随意了,男人不能说不行,女人不能说随便,所以这个不确定还是有些不妥。内核为此提供了一个强制设置的方法,就是通过内核启动参数console=来指定console使用某个确定的控制台,例如使用第二个串口/dev/ttyS2。这处理位置位于printk.c-:__setup("console=", console_setup);这样在register_console中就会为这个console开小灶,让它排在console_drivers链表的最开始。
三、为什么提示"can't access tty; job control turned off"
搜索一下busybox的代码,找到这个位置在busybox-1.14.2\shell\ash.c:setjobctl(int on)

        do { /* while we are in the background */
            pgrp = tcgetpgrp(fd);
            if (pgrp < 0) {
 out:
                ash_msg("can't access tty; job control turned off");
                mflag = on = 0;
                goto close;
            }
……
        } while (1);
也就是ash来查找自己的标准输入的进程组首领时没有找到,此时提示错误。该查询进入内核的接口为linux-2.6.21\drivers\char\tty_io.c
static int tiocgpgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t __user *p)
{
    /*
     * (tty == real_tty) is a cheap way of
     * testing if the tty is NOT a master pty.
     */
    if (tty == real_tty && current->signal->tty != real_tty)
        return -ENOTTY;
    return put_user(pid_nr(real_tty->pgrp), p);
}
这里是从tty->pgrp中查询一个tty设备的当前进程组进程。
四、为什么tty需要有一个pgrp
1、tty设备面临的问题
这里可以这么想:tty是一个设备,它的最基本功能就是接收和打印,比如从串口上来了一个字节,此时串口驱动可以游刃有余的接受到这个字节,但是此时这个字节发给谁呢?这个问题在用户看来很简单,当然是最前台的当前任务了。这就是不同的人看问题的角度不同,比方说年终考评的时候,A打给谁这个问题?你可能想,必须是打给我啊。但是领导可能想:这么多小弟,还是给那个最乖、最会拍马屁的那个吧。内核可以看到系统中所有的任务,所以对于一个确定串口,必须告诉它要把接受内容要发给哪个进程,这个设置是通过内核的linux-2.6.21\drivers\char\tty_io.c:tiocsctty函数实现,因为贴的代码太多了,这个就省略了,反正会把串口的pgrp设置为调用者就对了。
2、管道引导的进程组信号问题
管道可以像开火车一样连接一大串进程,这组线程就是属于同一个进程组(process group),如果没有什么意外,那么它们像芭蕾舞中天鹅湖一样快乐的手拉着手,但是现在假设说用户想终止这个管道,他/她可能按下ctrl+C,那么这个SIGINT是需要发给谁呢?如果只发给其中的一个,那可能就乱套了,其它的会不知所措的。此时就应该像《国产凌凌漆》中行刑的画面一样,大家刚好站成一排,然后每个士兵瞄准一个,预备之后没人一枪。也就是这个管道中的每个进程都会受到这样一个信号。这一点不是开玩笑的,内核中代码为:
static inline void isig(int sig, struct tty_struct *tty, int flush)
{
    if (tty->pgrp)
        kill_pgrp(tty->pgrp, sig, 1);这里也就是给进程组中的每个都发送一个信号。
    if (flush || !L_NOFLSH(tty)) {
        n_tty_flush_buffer(tty);
        if (tty->driver->flush_buffer)
            tty->driver->flush_buffer(tty);
    }
}
3、后台任务
shell中可以使用‘&’将一个或者一组任务放到后台运行,所谓后台运行就是它们不会获得tty设备的输入。相反,如果不加这个字符就是前台任务,前台任务的特点就是所有的设备输入不再交给shell,而是交给shell派生的任务。可以这么理解,如果shell抓权不放,那么子进程被架空,是后台线程;如果shell把tty控制权交给子进程,那么子进程就是前台任务。
或者说,这个tty有一个炮口,而tiocspgrp是调整这个炮口的位置,让它击中指定任务或者任务组。这一点在bash中是通过give_terminal_to函数中的
      if (tcsetpgrp (shell_tty, pgrp) < 0)
实现的。
五、为什么busybox打开/dev/console的时候有问题,而打开/dev/ttyS0之类没有问题
在上面描述的tty_open函数中对console的处理有下面一个赋值,
noctty = 1;
然后在该函数最后
if (!noctty &&
        current->signal->leader &&
        !current->signal->tty &&
        tty->session == NULL)
        old_pgrp = __proc_set_tty(current, tty);
对于未设置noctty的tty设备打开时,如果该设备还没有指定回话、并且当前进程没有设置控制终端,那就是一拍即合,把执行这个打开动作的任务设置为tty的前台任务。一般init派生的第一个进程是满足这个条件的,也就是这个设备被第一次打开,进程还没有控制终端。可以看到,由于内核启动的时候使用了/dev/console,所以内核是第一次打开这个设备的,一般不满足条件,所以不设置。
六、命令前加‘-’
如果修改为
/dev/console::respawn:-/bin/ash
注意在/bin/ash前加了一个‘-’,此时busybox就不再使用默认设置了,而是主动出击,通过系统调用设置,这样也是可以的
static void init_exec(const char *command)
{
    char *cmd[COMMAND_SIZE / 2];
    char buf[COMMAND_SIZE + 6];  /* COMMAND_SIZE+strlen("exec ")+1 */
    int dash = (command[0] == '-' /* maybe? && command[1] == '/' */);如果inittab中命令第一行为‘-’,则手动设置。即下面ioctl( TIOCSCTTY)
……
    if (dash) {
        /* _Attempt_ to make stdin a controlling tty. */
        if (ENABLE_FEATURE_INIT_SCTTY)
            ioctl(STDIN_FILENO, TIOCSCTTY, 0 /*only try, don't steal*/);
七、/dev/tty和/dev/console的区别
这个区别就好像一个镜子和一幅画的区别:看镜子的时候,每个人去看的时候看到的内容不同(都是看的那个人),而一幅画无论谁看都是相同的。这一点对于/proc/self同样适用,不同的人调用看到不同的内容,所以是一个动态的概念。/dev/tty是一个进程的一个属性,它一般是从父进程继承过来,但是正如前面所说,一个tty只能有一组前台进程,所以对于同一session中的大部分进程来说,从/dev/tty中可以读到自己的控制终端,但是如果自己不是前台任务的话,操作这个终端将会受到SIGTTIN信号,这个信号一般是致命的,会导致目标任务退出。对应代码
read_chan--->>>job_control
static int job_control(struct tty_struct *tty, struct file *file)
{
    /* Job control check -- must be done at start and after
       every sleep (POSIX.1 7.1.1.4). */
    /* NOTE: not yet done after every sleep pending a thorough
       check of the logic of this change. -- jlc */
    /* don't stop on /dev/console */
    if (file->f_op->write != redirected_tty_write &&
        current->signal->tty == tty) {
        if (!tty->pgrp)
            printk("read_chan: no tty->pgrp!\n");
        else if (task_pgrp(current) != tty->pgrp) {
            if (is_ignored(SIGTTIN) ||
                is_current_pgrp_orphaned())
                return -EIO;
            kill_pgrp(task_pgrp(current), SIGTTIN, 1);
            return -ERESTARTSYS;
        }
    }
    return 0;
}
/dev/console是整个内核级概念,超越进程,超越session,所有的任务打开这个东西都会看到相同的设备,这就使得天下大通的境界。
  评论这张
 
阅读(1723)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017