console和terminal是很容易让人迷惑的两个概念。要本质上区别这两个名词背后的差异,应该从它的使用角度去区分。 terminal(终端),其实是处于用户使用系统的角度来说的,相对于提供服务的系统终端是用户使用这个系统的入口,这个名词的使用范围比console要广。而console(控制台)则强调是控制系统(几乎就是大机了)的地方,其使用者主要是管理员。由于历史的的原因,在计算机世界里terminal和console常常指同一样东西,因为这种东西能完成两种角色,后来便发展到一些技术人员已经不严格称呼这样东西了。但在著述文档时,应该要根据上下文环境决定使用哪个名词。
举个例子:对于摄像系统,摆在监控室里面的那些就是terminal而不是console.手机也是一个terminal,它是用户使用通信系统的入口,而手机这个小系统也是可以通过console来管理的。
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,而后台进程会收到SIGTtiN或SIGTTOU ,如果它们读写该终端的话。被同一个终端控制的所有进程被称为一个会话(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)。我们可以通过一个实例看一下如何使用:
[cpp] view plain copy
- #include
- #include
- #include
- #include
- #include
-
- #include
- #include
- #include
-
- #include
-
-
- char *mptname = "/dev/ptmx"; /* master pseudo-tty device */
- //...
- int master,slave;
-
- struct termios ***uf;
- struct winsize size;
-
- void getmaster()
- {
- struct stat stb;
- if ((master = open(mptname, O_RDWR))>= 0) { /* a pseudo-tty is free */
- (void) ioctl(0, TCGETS, (char *)&***uf);
-
- (void) ioctl(0, TIOCGWINSZ, (char *)&size); /// TIOCGWINSZ 获得终端设备的窗口大小 get
- return;
- } else { /* out of pseudo-tty's */
- perror(mptname);
- fprintf(stderr, gettext("Out of pseudo-tty'sn"));
- exit(1);
- }
- }
-
- void getslave()
- {
- char *slavename; /* name of slave pseudo-tty */
- grantpt(master); /* change permissions of slave */
- unlockpt(master); /* unlock slave */
- slavename = ptsname(master); /* get name of slave */
- slave = open(slavename, O_RDWR); /* open slave */
- if (slave <0) { /* error on opening slave */
- perror(slavename);
- exit(1);
- }
-
- ioctl(slave, I_PUSH, "ptem"); /* push pt hw emulation module */
- ioctl(slave, I_PUSH, "ldterm"); /* push line discipline */
-
- (void) ioctl(slave, TCSETSF, (char *)&***uf);
- (void) ioctl(slave, TIOCSWINSZ, (char *)&size);
- }
然后我们再来看一下glibc中对ptsname(3)的实现:
(源文件sysdeps/unix/sysv/linux/ptsname.c)
[cpp] view plain copy
- /** Return the pathname of the pseudo terminal slave associated with
- the master FD is open on, or NULL on errors.
- The returned storage is good until the next call to this function. */
- char * ptsname (int fd)
- {
- static char peername[1024]; /** XXX */
- error_t err;
-
- err = __ptsname_r (fd, peername, sizeof (peername));
- if (err)
- __set_errno (err);
-
- return err ? NULL : peername;
- }
-
-
- /** Store at most BUFLEN characters of the pathname of the slave pseudo
- terminal associated with the master FD is open on in BUF.
- Return 0 on success, otherwise an error number. */
- int
- __ptsname_r (int fd, char *buf, size_t buflen)
- {
- char peername[1024]; /** XXX */
- size_t len;
- error_t err;
-
- peername[0] = '
|