控制命令如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. {
  5. sigset_t set, oset;
  6. int r, e;
  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. 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. if (tty->pgrp)                           //关联到bash进程中前台进程组的id
  13. kill_pgrp(tty->pgrp, signal, 1); //向前台进程组发送信号
  14. return;
  15. }

总结:

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

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

shell 前台进程组的选择的更多相关文章

  1. 二十八、Linux 进程与信号---前台进程组

    28.1 介绍 28.1.1 概念 自动接受终端信号的组称为前台进程组 在终端通过 ctrl + c 等动作产生的信号首先被前台进程组接受 在 shell 启动的若干个进程组默认是父进程所在的组为前台 ...

  2. 机器学习入门-随机森林预测温度-不同参数对结果的影响调参 1.RandomedSearchCV(随机参数组的选择) 2.GridSearchCV(网格参数搜索) 3.pprint(顺序打印) 4.rf.get_params(获得当前的输入参数)

    使用了RamdomedSearchCV迭代100次,从参数组里面选择出当前最佳的参数组合 在RamdomedSearchCV的基础上,使用GridSearchCV在上面最佳参数的周围选择一些合适的参数 ...

  3. 安装ipython,使用scrapy shell来验证xpath选择的结果 | How to install iPython and how does it work with Scrapy Shell

    1. scrapy shell 是scrapy包的一个很好的交互性工具,目前我使用它主要用于验证xpath选择的结果.安装好了scrapy之后,就能够直接在cmd上操作scrapy shell了. 具 ...

  4. 使用shell脚本自定义实现选择登录ssh

    在系统bin目录中建立两个脚本分别是pssh tssh pssh #!/usr/bin/expect -f set ip [lindex ] set port [lindex ] set passwo ...

  5. 【11NOIP提高组】选择客栈(信息学奥赛一本通 1546)(洛谷 1311)

    题目描述 丽江河边有nn家很有特色的客栈,客栈按照其位置顺序从 11到nn编号.每家客栈都按照某一种色调进行装饰(总共 kk 种,用整数 00 ~k-1k−1 表示),且每家客栈都设有一家咖啡店,每家 ...

  6. 洛谷P1311 [NOIP2011提高组Day1T2]选择客栈

    P1311 选择客栈 题目描述 丽江河边有n 家很有特色的客栈,客栈按照其位置顺序从 1 到n 编号.每家客栈都按照某一种色调进行装饰(总共 k 种,用整数 0 ~ k-1 表示),且每家客栈都设有一 ...

  7. 【NOIP2011提高组】选择客栈

    题目不附了,是一个单纯的ST模型,但是考验各种常数优化. 最大的优化是对于同颜色的客栈来说,如果1号和2号成功配对了,那么1和3,1和4都可以成功配对,那么只要找到一对成功配对的,我们就直接加上剩下的 ...

  8. [NOIP提高组2011day1t2]选择客栈

    我看到有人用线段树来写而且想法和我的差不多,但是代码有一点复杂,所以我就贴一下我的做法. 思路 首先一定知道纯暴力50分差不多了,所以看到k非常的小,那么就从k入手. 不知道有没有人和我一样是先枚举颜 ...

  9. e769. 在按钮组中选择一个单选按钮

    // To create a radio button and button group, // see e768 创建单选按钮 // Select the radio button; the cur ...

随机推荐

  1. ehlib ado 删除选中记录 的方法

    procedure TForm1.Button1Click(Sender: TObject); var I: Integer; begin do begin DBGridEh1.DataSource. ...

  2. MyEclipse移动开发教程:设置所需配置的iOS应用(三)

    MyEclipse个人授权 折扣低至冰点!立即开抢>> [MyEclipse最新版下载] 三.创建配置文件 Provisioning profiles授权文件应用程序在iOS设备上安装并运 ...

  3. L213

    The world lost seven astronauts of Space Shuttle Columbia(哥伦比亚号航天飞机) this month. It broughthome the ...

  4. SWIFT中函数返回值为Tuple

    在playgroundm内键入以下代码,求一个成绩数组内最大分值和最小分值 func maxminScore(scores:Array<Int>) -> (maxScore:Int, ...

  5. django中的分页器组件

    目录 django的组件-分页器 引入分页器 分页器demo 创建数据库模型 url控制器 views视图函数 templates模板 为什么要用分页器 导入分页器 分页器优化1 分页器优化2 有多少 ...

  6. iOS-----推送机制(下)

    推 送 机 制(下) 单击”从证书颁发机构请求证书”后,将会显示下图所示的对话框 输入电子邮件地址和常用名称,并选中“存储到磁盘”单选钮,然后单击“继续”按钮,该程序将会创建一个“Certificat ...

  7. CentOS7安装OpenStack(Rocky版)-04.安装Nova计算服务(控制节点)

    上一篇文章分享了glance镜像服务的安装配置,本文主要分享openstack的计算服务Nova的安装和配制方法 ------------------ 完美的分割线 ----------------- ...

  8. 2018C语言助教总结

    回顾 很荣幸得到各位老师的认可,担任计科3班和4班的C语言课程助教,很感谢车老师和牛老师一学期的帮助,使得我更好的担任助教一职.我班学生59名,很愉快的与同学们度过一个美好的学期,其实作为助教同样从学 ...

  9. opencv-python教程学习系列4-opencv绘图函数

    前言 opencv-python教程学习系列记录学习python-opencv过程的点滴,本文主要介绍opencv绘图函数,坚持学习,共同进步. 系列教程参照OpenCV-Python中文教程: 系统 ...

  10. Linux基础和网络管理上机试题 - imsoft.cnblogs

    一.(使用at命令实现任务的的自动化,要求用一条条的指令完成)      找出系统中任何以txt为后缀名的文档,并且进行打印.打印结束后给用户foxy发出邮件通知取件.指定时间为十二月二十五日凌晨两点 ...