zoukankan      html  css  js  c++  java
  • shell 前台进程组的选择

    控制命令如ctrl+c,ctrl+d等命令是会kill到前台进程组的,这个过程和bash进程还有tty驱动有关系。

        在终端执行命令,在bash进程看来都是在执行job,然后fork出子进程来执行这些job,引用一下http://blog.csdn.net/ruglcc/article/details/8574113文章的中bash架构图,说明一下bash进程

        

        bash进程来源于login进程,login进程来源于getty进程或者telnetd进程,从上图中可以看到,bash进程在启动之后,首先加载一系列的配置(具体可以参考源代码,地址http://ftp.gnu.org/gnu/bash/),最后阶段就readloop,循环等待/dev/tty是否有读数据,也就是看下终端是否有输入的命令。如果有输入就通过flex bison来解析数据,然后看下是否要去fork子进程去执行任务。

        bash进程会把当前的终端和fork出来的子进程做一下关联,这个过程就是进程组获得了控制终端的过程。最近查了很多关于孤儿进程组的一些概念,里面不清楚的部分就是说一个进程如何来获得终端的控制。很多文章都是没有说到这一点。

        很显然bash进程有权来选择是哪个进程将获得终端控制,函数tcsetpgrp(int fd,pid_t pgrp_id),这个函数可以设置把进程组id和控制终端的fd(打开/dev/tty)作关联。这里做关联的意义在于,在进程控制终端的过程中,对于键盘产生的输入,如ctrl+c,  ctrl+d,会送到tty 的驱动中,然后驱动看到键盘的输入,会根据输入来确定发送具体的信号到和tty关联的进程组中去。

        bash进程在readloop的过程中,对bash的源代码还没大看懂,大概就是碰到执行命令,有些job会fork出子进程来执行。一些job在fork子进程后会去调用exec函数,在之前会把这个子进程和tty作关联。

        看下bash进程中start job之前,会判断如果是前台job,那么就会把前台进程组的组id和tty做关联。

    Bash4.2 源代码:jobs.c start_job函数

    1. if (foreground) //如果是在前台执行的进程组
    2.     {
    3.       get_tty_state ();
    4.       save_stty = shell_tty_info;
    5.       /* Give the terminal to this job. */
    6.       if (IS_JOBCONTROL (job))
    7.         give_terminal_to (jobs[job]->pgrp, 0); //jobs[job]->pgrp就是前台进程组组id
    8.     }

        可以看到bash进程在执行job的过程之前,把执行job的从shell进程fork出的前端进程组和tty关联了起来,再看下give_terminal_to函数的逻辑,主要是调用tcsetpgrp函数,把进程组组id和tty驱动中的tty结构关联起来。

    1. give_terminal_to (pgrp, force)
    2.      pid_t pgrp;
    3.      int force;
    4.   sigset_t set, oset;
    5.   int r, e;
    6.   
    7.   r = 0;
    8.   if (job_control || force) 
    9.     { 
    10.       sigemptyset (&set);
    11.       sigaddset (&set, SIGTTOU);
    12.       sigaddset (&set, SIGTTIN);
    13.       sigaddset (&set, SIGTSTP);
    14.       sigaddset (&set, SIGCHLD);
    15.       sigemptyset (&oset);
    16.       sigprocmask (SIG_BLOCK, &set, &oset);
    17.       
    18.       if (tcsetpgrp (shell_tty, pgrp) < 0) //调用tcsetpgrp函数。

        shell_tty就是当前shell进程打开/dev/tty的文件描述符,这tcsetpgrp函数在glibc中调用的ioctl函数。

    代码:glibc   /sysdeps/unix/bsd/tcsetpgrp.c

    1. int tcsetpgrp (fd, pgrp_id)
    2.      int fd;
    3.      pid_t pgrp_id;
    4. {
    5.   return __ioctl (fd, TIOCSPGRP, &pgrp_id); //调用了ioctl函数 
    6. }

        内核中对tty的ioctl的实现的逻辑:

    1. long tty_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
    2. {
    3. struct tty_struct *tty = file_tty(file); 
    4.         struct tty_struct *real_tty;
    5.         void __user *p = (void __user *)arg;
    6.         int retval;
    7.         struct tty_ldisc *ld;
    8.         struct inode *inode = file->f_dentry->d_inode;
    9.         if (tty_paranoia_check(tty, inode, "tty_ioctl"))
    10.                 return -EINVAL;
    11.         real_tty = tty_pair_get_tty(tty);
    12.        switch (cmd) {
    13.             case TIOCSPGRP :                     //进程组关联tty
    14.                 return tiocspgrp(tty, real_tty, p);

        关键是函数tiocspgrp设置tty和进程组的关系

    1. static int tiocspgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t __user *p)
    2. {
    3.         struct pid *pgrp;
    4.         pid_t pgrp_nr;
    5.         int retval = tty_check_change(real_tty);
    6.         unsigned long flags;
    7.         if (retval == -EIO)
    8.                 return -ENOTTY;
    9.         if (retval)
    10.                 return retval;
    11.         if (!current->signal->tty ||
    12.             (current->signal->tty != real_tty) ||
    13.             (real_tty->session != task_session(current))) //做下check
    14.                 return -ENOTTY;
    15.         if (get_user(pgrp_nr, p))
    16.                 return -EFAULT;
    17.         if (pgrp_nr < 0)
    18.                 return -EINVAL;
    19.         rcu_read_lock();
    20.         pgrp = find_vpid(pgrp_nr); 
    21.         retval = -ESRCH;
    22.         if (!pgrp)
    23.                 goto out_unlock;
    24.         retval = -EPERM;
    25.         if (session_of_pgrp(pgrp) != task_session(current))
    26.                 goto out_unlock;
    27.         retval = 0;
    28.         spin_lock_irqsave(&tty->ctrl_lock, flags);
    29.         put_pid(real_tty->pgrp);
    30.         real_tty->pgrp = get_pid(pgrp);                    //关联tty和pgrp的关系
    31.         spin_unlock_irqrestore(&tty->ctrl_lock, flags);
    32. out_unlock:
    33.         rcu_read_unlock();
    34.         return retval;
    35. }

        通过上面从bash进程在start job之前的调用give_terminal函数,到glibc中的tcsetpgrp函数,再到内核中的ioctl函数,设置了进程组和tty的关联。这样tty驱动在接收输入的时候,会判断接收的字符,如果接收到的是控制字符,例如ctrl+c,ctrl+d,那么在tty驱动中会调用killpg函数,直接把相应的信号kill到进程组中,这样就是我们看到的当前台进程组运行时,按ctrl+c等控制命令时,终端就会直接控制到那个前台进程组了。

        看下tty驱动中的kill进程组的代码:

        /drivers/tty/ntty.c n_tty_receive_char函数

    1. if (L_ISIG(tty)) {
    2.                 int signal;
    3.                 signal = SIGINT; /
    4.                 if (c == INTR_CHAR(tty)) 
    5.                         goto send_signal;
    6.                 signal = SIGQUIT; 
    7.                 if (c == QUIT_CHAR(tty))
    8.                         goto send_signal;
    9.                 signal = SIGTSTP;
    10.                 if (c == SUSP_CHAR(tty)) {
    11. send_signal:
    12.                         
    13.                         if (tty->pgrp)                           //关联到bash进程中前台进程组的id
    14.                                 kill_pgrp(tty->pgrp, signal, 1); //向前台进程组发送信号
    15.                         return;
    16.                 }

    总结:

        从上面可一看到,前台进程组控制终端的本源就是在bash进程fork出子进程之后,在执行前台job之前,会把那个进程组和tty做关联,这样tty驱动在得到输入的时候,碰到那些控制字符,那么就直接kill信号到那个关联的进程组了。以上的分析,纯属个人意见,如有分析不当的地方,希望大家指出。

    参考文章:
    1.http://blog.csdn.net/chenyu105/article/details/7738388
    2.http://blog.csdn.net/ruglcc/article/details/8574113

  • 相关阅读:
    nginx配置ssl并结局TP3.2路由pathinfo
    TP3.2公共模板
    linux 上mysql慢日志查询
    RBAC流程
    Linux下安装Lnmp环境
    php操作redis命令大全
    Opencv无法调用cvCaptureFromCAM无法打开电脑自带摄像头
    c++考研复习之非递归前序中序后序遍历二叉树
    学习《Numpy基础知识》
    学习《Numpy快速教程
  • 原文地址:https://www.cnblogs.com/jiangzhaowei/p/4169290.html
Copyright © 2011-2022 走看看