设为首页收藏本站

数码鹭岛论坛

 找回密码
 注-册

QQ登录

只需一步,快速开始

搜索
查看: 1617|回复: 0
打印 上一主题 下一主题

Linux的控制台(TTY/PTY)与多任务(MULTI-TASKING)

[复制链接]
跳转到指定楼层
1#
发表于 2014-10-2 21:59:59 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
原文: [url=https://blog.kghost.info/2012/08/20/tty-multi-tasking/]https://blog.kghost.info/2012/08/20/tty-multi-tasking/

Linux是一个多任务操作系统,可以方便的在一个控制台(或shell)下同时执行多条命令,达到这样的目标并不是一件容易的事情。本文帮助理解下面几个跟控制台有关的概念:tty/pty,control terminal,session,process group,signal;并设计实现一个多任务控制程序

bash 的多任务支持
熟悉Linux的同学都知道,bash支持同时跑多个任务,下面先简单演示一下 bash 的多任务。首先,运行一个 cat 命令,然后通过 Ctrl+Z 中断这个 cat 回到 bash:
$ cat^Z[1]+  Stopped                 cat
然后,我们可以再运行一个 cat 任务,这回让 cat 干点活,继续用 Ctrl+Z 中断:
$ cat /dev/urandom > /dev/null^Z[2]+  Stopped                 cat /dev/urandom > /dev/null
这样我们就有了两个 cat 任务在后台,通过 bash 内置的 jobs 命令可以查看后台的任务:
$ jobs[1-  Stopped                 cat[2]+  Stopped                 cat /dev/urandom > /dev/null
大家可以看到,这两条命令都处于 Stopped 状态,现在我们通过 bg 命令让第二个 cat 在后台运行:
$ bg 2[2]+ cat /dev/urandom > /dev/null &$ jobs[1]+  Stopped                 cat[2-  Running                 cat /dev/urandom > /dev/null &
这时可以看到,第二个 cat 变成蓝 Running 状态,同时我机器的 CPU 温度也飚上来了。我们还可以通过 fg 命令让第一个 cat 到前台执行:
$ fg 1cat
现在这个 cat 又恢复到等待我的输入的状态了。这样看来,好像 multi-tasking 是一件非常简单的事情,那么请尝试回答下面几个问题:
Q1. 如何中断/继续程序的运行?Q2. 如何防止后台运行的程序互相抢夺控制台输入?TTY - signal 转换
首先,回答第一个问题
Q1. 如何中断/继续程序的运行?
很简单,通过两个 signal 就可以控制程序中断/运行:
$ man 7 signal       ...       SIGCONT   19,18,25    Cont    Continue if stopped       SIGSTOP   17,19,23    Stop    Stop process       ...
于是某同学就在 bash 的代码里面找关于这两个 signal 的代码,但是他只找到了 fg/bg 命令给程序发送了 SIGCONT,没有找到关于 SIGSTOP 的代码,于是有了下面的问题:
Q1.1: 什么程序给 cat 发送了 SIGSTOP 信号?
通过本小节标题也可以看出,signal 其实是 TTY 发送的:
$ stty -aspeed 38400 baud; rows 51; columns 185; line = 0;.... susp = ^Z; ............
看到了里面的 susp = ^Z 了么,这个设置的意思就是一旦 TTY 收到了 Ctrl-Z 就会把其转换成 SIGSTOP 信号?同理我们可以设置 Ctrl-N 为挂起的快捷键
$ stty susp ^N$ cat^N[1]+  Stopped                 cat
由于所有的信号都是 tty 控制的,我们也可以修改 Ctrl-C/S/Q 等所有信号快捷键的行为,那么这里又有了下面这个问题:
Q3. 哪些程序会收到 TTY 来的信号?如果当前 TTY 来的信号所有程序都能收到,那么 Ctrl-C 会不会连后台程序一起杀掉?
这个问题先卖个关子,下面介绍一下process group。
process group
这里先启动一些进程:
$ google-chrome &[1 26077$ cat | cat
我在这个 bash session 里面启动了3个进程,一个 google-chrome,两个 cat。chrome 又继续启动了很多进程。为了方便理解画张图,理清 TTY,session 与 process group 之间的关系:
              ┌─────────────────────────────────────┐              │ Session                             │┌─────┐  One  │  ┌────────────────────────────────┐ ││ TTY │<──on─>│  │ Process group                  │ │└─────┘  One  │  │   bash                         │ │              │  └────────────────────────────────┘ │              │  ┌────────────────────────────────┐ │              │  │ Active process group           │ │              │  │   cat                          │ │              │  │   cat                          │ │              │  └────────────────────────────────┘ │              │  ┌────────────────────────────────┐ │              │  │ process group                  │ │              │  │   /opt/google/chrome/chrome    │ │              │  │    \_ /opt/google/chrome/chrome│ │              │  │    \_ /opt/google/chrome/chrome│ │              │  │    \_ /opt/google/chrome/chrome│ │              │  └────────────────────────────────┘ │              └─────────────────────────────────────┘
每个 TTY 对应一个 session,session 是由 login (ssh/getty/…) 程序创建的,bash 仅仅是继承了这个 session。每个 session 里面有若干个 process group,每当 bash 创建(fork)一个进程的时候,在运行程序(exec*)前,都会为这个进程创建一个新的 process group。每个 process group 中只有一个 group 处于 active 状态,由 bash 控制哪个进程处于 active 状态。每个 process group 里面有若干个进程。为什么要这样设计,且听我慢慢道来。大家是否还记得前面留下的两个悬而未决的问题:
Q2. 如何防止后台运行的程序互相抢夺控制台输入?Q3. 哪些程序会收到 TTY 来的信号?如果当前 TTY 来的信号所有程序都能收到,那么 Ctrl-C 会不会连后台程序一起杀掉?
有了 process group 的概念,回答这两个问题就非常简单了,只有处于 Active 内的进程能够获取控制台输入,并且 signal 只发送给 Active 内的进程。当有后台运行的进程想要读取控制台的时候(比如后台运行一个 cat),tty 会向其发送 SIGSTOP 信号,挂起此程序,你会发现,企图让 cat 后台运行是不可能的。当用户按下 Ctrl-C 或者 Ctrl-Z 的时候,只有前台的进程会收到相应的信号,并执行相应的操作。由于 bash 的特殊设计,用户是无法让两个程序同时读取控制台的,如果有人实在无聊,写了个程序 fork 出几个前台进程读取控制台(比如笔者),还是会出现抢占的现象:
int main() {        if (fork())                execlp("awk", "awk", "{ print \"parent:\" $0 }", 0);        else                execlp("awk", "awk", "{ print \"child:\" $0 }", 0);}
用户的输入会随机传给 parent 或者 child,并且结果是不可预测的。
多任务控制的实现
前面介绍的多任务控制的原理,下面我们设计一个多任务控制程序,实现和 bash 一样的功能:
  • 在前台有任务执行的时候等待

set processGroupsint startCommand(char *cmd) {  int pid = fork();  if (pid) {    int = waitpid(pid); // 等待命令返回(结束或者挂起)    return status;  } else {    int mypid = getpid()    pgid = setpgid(); // 建立新的 process group    tcsetpgrp(pgid); // 设置当前 group 为 active    processGroups.add(mypid)    exec(cmd); // 运行命令  }}
  • 可以随时挂起前台执行的任务

挂起程序完全由 tty 完成,只需要在 waitpid 后面检查前台程序是结束了,还是被挂起了,如果是挂起了,需要把控制台输入权限返还给 bash:
...switch(startCommand(cmd)) {case exit:  processGroups.remove(pid);  break;case suspend:  break; // do nothing}tcsetpgrp(getpgid()); // 拿回 active process group
  • bg 命令切换后台执行任务

只需要向程序发送 SIGCONT 信号:
void bg(pid) {  if (issuspended(pid))    kill(pid, SIGCONT);}
  • fg 命令把任务切换到前台

先让程序继续执行,然后转移 active process group:
int fg(pid) {  bg(pid);  tcsetpgrp(getpgid(pid));  return waitpid(pid);}
  • jobs 命令查看当前任务

for (pid in processGroups)  print pid;
这样我们就实现了一个多任务控制程序,由于 tty 设备的存在,使得实现多任务控制轻松了很多。Windows 下是没有 tty 设备的,于是控制台程序就有很多限制,比如无法实现一个能够获取 Ctrl-C 输入的控制台程序,只有去拦截 interrupt 信号。上面这些内容估计只有 bash 的设计师才会关注,但是下面的内容就是几乎每个 linux 程序员都会关注的内容。
daemon 程序的实现
daemon 进程就是要与 TTY 划开界限,所有东西都不依赖 TTY,那么结果就非常简单了,因为 TTY 和 session 是一一对应的关系,我们新建一个 session 就等于把与原来 TTY 有关的东西完全抛开了:
void daemonize(char *cmd) {  close(0);  close(1);  close(2);  if (fork())    exit(0);  else {    setsid();    exec(cmd);  }}
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏 分享分享
您需要登录后才可以回帖 登录 | 注-册

本版积分规则

小黑屋|手机版|Archiver|数码鹭岛 ( 闽ICP备20006246号 )  

counter

GMT+8, 2025-12-4 02:09 , Processed in 0.069349 second(s), 23 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表