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

  • 相关阅读:
    668. Kth Smallest Number in Multiplication Table
    658. Find K Closest Elements
    483. Smallest Good Base
    475. Heaters
    454. 4Sum II
    441. Arranging Coins
    436. Find Right Interval
    410. Split Array Largest Sum
    392. Is Subsequence
    378. Kth Smallest Element in a Sorted Matrix
  • 原文地址:https://www.cnblogs.com/jiangzhaowei/p/4169290.html
Copyright © 2011-2022 走看看