Linux下的console和terminal
consoleterminal是很容易让人迷惑的两个概念。根据wikipedia上的定义,小型计算机的console应该就是键盘加显示器;而terminal则是输入数据进去,和显示数据来源的设备,通常是一个计算机系统。
Linux下的console除了真实的硬件设备外,还有virtual console,也就是你按alt+Fn或者alt+ctrl+Fn切换到的东西。所谓虚拟就是这些console共享同一个真实的设备,只有一个活动的console才显示在前面。这些console对应的设备是:/dev/ttyN,其中1 N 63。而/dev/tty0则是指向当前的terminal/dev/console是指向当前console,但它现在并_不是_/dev/tty0的符号链接。更多可参考console(4)
/dev/tty是另一个特殊设备,它指向控制终端(controlling terminal)。如果某个进程的控制终端是/dev/tty3,那么/dev/tty就指向/dev/tty3了。控制终端是什么概念?它是一个进程的某个属性,是依附带该进程上的终端。比如我们在某个终端下输入ctrl+C,那么它控制的前台进程就会收到SIGINT,而后台进程会收到SIGTTINSIGTTOU ,如果它们读写该终端的话。被同一个终端控制的所有进程被称为一个会话(session),会话的领导就是创建改会话
的进程,其子进程也会被该终端控制。所以,1) 需要交互的命令行程序通常会从/dev/tty这个设备进行读写;2) Unix后台进程都需要在fork之后调用setsid(2)3) 需要加O_NOCTTY,当你open一个可能是终端的文件时。
另外,想要确定/dev/tty究竟是指向哪个设备,可以调用TIOCCONS ioctl。参考tty(4)
下面是另外一个概念——伪终端(pseudo-terminal),根据pty(7)的介绍,伪终端一对虚拟设备,提供端到端双向通信的通路,一端称为master,另一端称为slave。在slave那端看到的和在真实终端看到的效果一样。所以伪终端一般被ssh等网络登录程序使用。历史上,有两套伪终端接口,一个是Unix 98伪终端,另一个是BSD伪终端。
BSD提供的接口很简单:/dev/pty[p-za-e][0-9a-f] master; /dev/tty[p-za-e][0-9a-f] slave,它们都是配好对的。这样看起来很简单,但对程序员来说不容易,要到一个合适的终端需要一个个从头尝试。所以这种方式已经被遗弃。而Unix 98伪终端则完全不同,它始终使用/dev/ptmx作为master复制设备,然后在每次打开它的时候才得到一个master设备的fd,同时在/dev/pts/目录下得到一个slave设备。这样编程就相对容易了,根据pts(4)介绍,需要三个新的API ptsname(3)grantpt(3)unlockpt(3)。我们可以通过一个实例看
一下如何使用:
(以下代码摘自netvirt
PLAIN TEXT
C:
1. char    *mptname = "/dev/ptmx"; /* master pseudo-tty device */
2. //...
3. void
4. getmaster()
5. {
6.     struct stat stb;
7.  
8.     if ((master = open(mptname, O_RDWR))>= 0) { /* a pseudo-tty is free */
9.         (void) ioctl(0, TCGETS, (char *)&b);
10.         (void) ioctl(0, TIOCGWINSZ, (char *)&size);
11.         return;
12.     } else {                /* out of pseudo-tty's */
13.         perror(mptname);
14.         fprintf(stderr, gettext("Out of pseudo-tty's\n"));
15.         fail();
16.     }
17. }
18.  
19. void
20. getslave()
21. {
22.     char *slavename;    /* name of slave pseudo-tty */
23.  
24.     grantpt(master);        /* change permissions of slave */
25.     unlockpt(master);          /* unlock slave */
26.     slavename = ptsname(master);        /* get name of slave */
27.     slave = open(slavename, O_RDWR);    /* open slave */
28.     if (slave <0) {        /* error on opening slave */
29.         perror(slavename);
30.         fail();
31.     }
32.     ioctl(slave, I_PUSH, "ptem")/* push pt hw emulation module */
33.     ioctl(slave, I_PUSH, "ldterm");    /* push line discipline */
34.  
35.     (void) ioctl(slave, TCSETSF, (char *)&b);
36.     (void) ioctl(slave, TIOCSWINSZ, (char *)&size);
37. }
然后我们再来看一下glibc中对ptsname(3)的实现:
(源文件sysdeps/unix/sysv/linux/ptsname.c
PLAIN TEXT
C:
1. #define _PATH_DEVPTS "/dev/pts/"
2.  
3. char *
4. ptsname (int fd)
5. {
6.   return __ptsname_r (fd, buffer, sizeof (linux终端下载软件buffer)) != 0 ? NULL : buffer;
7. }
8.  
9. int
10. __ptsname_r (int fd, char *buf, size_t buflen)
11. {
12.  
13. ...
14.  
15.   if (__ioctl (fd, TIOCGPTN, &ptyno) == 0)
16. ...
17.      numbuf[sizeof (numbuf) - 1] = '\0';
18.       p = _itoa_word (ptyno, &numbuf[sizeof (numbuf) - 1], 10, 0);
19. ...
20.       memcpy (__stpcpy (buf, devpts), p, &numbuf[sizeof (numbuf)] - p);
我们可以看出,实际上是调用ioctl TIOCGPTN,通过内核,而Linux内核又是通过devpts这种文件系统实现了这一切:
$ mount | grep devpts
devpts on /dev/pts type devpts (rw,gid=5,mode=620)
这样我们终于把一切搞清楚了。:-)
If you're new here, you may want to subscribe to my RSS feed. Thanks for visiting!
This entry was posted on Monday, November 10th, 2008 at 7:01 am and is filed under Linux Application. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。