⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 19.htm

📁 UNIX环境下C编程的详细详细介绍
💻 HTM
📖 第 1 页 / 共 4 页
字号:
<html>

<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<meta name="GENERATOR" content="Microsoft FrontPage 3.0">
<title>C:\WINDOWS\Desktop\UnixProg\7.htm</title>
</head>

<body>
<font SIZE="2">

<h1 align="center">第十九章 伪终端 </h1>

<p>19.1 引言 </p>

<p>在第九章中我们介绍了进行终端登录时,需要通过一个终端设备自动提供终端</p>

<p>的语 </p>

<p>义。在终端和运行程序之间有一个终端行规程(图11.2),通过这个规程我</p>

<p>们能够 </p>

<p>在终端上设置特殊字符(退格、行删除、中断等)。但是,当一个登录请求到</p>

<p>达网 </p>

<p>络连接时,终端行规程并不是自动被加载到网络连接和登录程序shell之间</p>

<p>的。图 </p>

<p>9.5显示了有一个伪终端设备驱动程序被用来提供终端语义。 </p>

<p>除了用于网络登录,伪终端还被用在其他方面,我们将在本章中进行介绍。我</p>

<p>们 </p>

<p>将首先提供在SVR4和4.3+BSD系统下用于创建伪终端的函数,然后使用这些</p>

<p>函数编 </p>

<p>写一个程序用来调用pty。我们将看到这个程序的不同使用:在输入字符和终</p>

<p>端显 </p>

<p>示之间进行转换(BSD的码转换程序)和运行协同进程来避免我们在程序</p>

<p>14.10中遇 </p>

<p>到的缓冲区问题。 </p>

<p>19.2 概述 </p>

<p>伪终端这个名词暗示了与一个应用程序相比,它更加象一个终端。但事实上,</p>

<p>伪终 </p>

<p>端并不是一个真正的终端。图19.1显示了使用伪终端的进程的典型结构。其</p>

<p>中关键 </p>

<p>点如下: </p>

<p>图19.1 使用伪终端的典型进程结构 </p>

<p>1 通常一个进程打开伪终端主设备然后调用fork。子进程建立了一个新的会</p>

<p>话,打 </p>

<p>开一个相应的伪终端从设备,将它复制成标准输入、标准输出和标准出错,然</p>

<p>后调 </p>

<p>用exec。伪终端从设备成为子进程的控制终端。 </p>

<p>2 
对于伪终端从设备之上的用户进程来说,其标准输入、标准输出和标准出</p>

<p>错都能 </p>

<p>当作终端设备使用。用户进程能够调用11章中讲到的所有输入/输出函数。但</p>

<p>是因 </p>

<p>为在伪终端从设备之下并没有真正的设备,无意义的函数调用(改变波特率、</p>

<p>发送 </p>

<p>中断符、设置奇偶校验等)将被忽略。 </p>

<p>3 
任何写到伪终端主设备的输入都会作为在从设备端的输入,反之亦然。事</p>

<p>实上所 </p>

<p>有从设备端的输入都来自于主设备上的用户进程。这看起来就象一个流管道</p>

<p>(图1 </p>

<p>5.3),但从设备上的终端行规程使我们拥有普通管道之外的其他处理能力。</p>

<p>图19.1显示了BSD系统中的伪终端结构。在19.3.2中我们将看到如何打开这</p>

<p>些设备 </p>

<p>。 </p>

<p>在SVR4系统中伪终端是使用流系统来创建的(12.4节)。图19.2详细描述了</p>

<p>SVR4系 </p>

<p>统中各个伪终端模块之间的关系。在虚线框中的两个流模块是可选的。请注意</p>

<p>在从 </p>

<p>设备上的三个流模块同12.10网络登录程序的输出是一样的。在19.3.1小节</p>

<p>中将介 </p>

<p>绍如何组织这些流模块。 </p>

<p>从现在开始将简化以上图示,首先我们不再画出图19.1的&quot;读、写功能&quot;或图</p>

<p>19.2 </p>

<p>的流首。我们还使用缩写&quot;pty&quot;表示伪终端,并将图19.2中所有伪终端从设</p>

<p>备之上 </p>

<p>的流模块集合表示为&quot;终端行规程&quot;模块。 </p>

<p>图19.2 在SVR4下的伪终端组织结构 </p>

<p>现在来看一下伪终端的几种典型用法。 </p>

<p>网络登录服务器 </p>

<p>伪终端用于构造网络登录服务器。典型的例子是telnetd和rlogind服务器。</p>

<p>在St </p>

<p>evens[1990]的第15章中详细讨论了提供rlogin服务的步骤。一旦登录</p>

<p>shell运行在 </p>

<p>远端主机上,我们得到如图19.3的结构。同样的结构也用于telnetd服务</p>

<p>器。 </p>

<p>在rlogind服务器和登录shell之间有两个exec调用,这是因为login程序通</p>

<p>常是在 </p>

<p>两个exec之间检验用户是否合法的。 </p>

<p>图19.3 rlogind服务器的进程组织结构 </p>

<p>本图的一个关键点是驱动伪终端主设备的进程通常同时在读写另一个输入/输</p>

<p>出流 </p>

<p>。在本例中另一个输入/输出流是TCP/IP。这表示该进程必然使用了某种形式</p>

<p>的如 </p>

<p>select或poll那样的输入/输出多路转接(节12.5),或被分成两个进程。</p>

<p>请回忆 </p>

<p>我们在18.7节讨论过的一个进程和两个进程的的比较。 </p>

<p>script程序 </p>

<p>script程序是随SVR4和4.3+BSD提供的,该程序将终端会话期间所有的输入</p>

<p>和输出 </p>

<p>信息在一个文件中做一个拷贝。它通过将自己置于终端和登录shell的一个新</p>

<p>的调 </p>

<p>用之间来完成这个工作。图19.4详细描述了script程序相关的交互。这里我</p>

<p>们特别 </p>

<p>指出script程序通常是从登录shell启动的,该shell然后等待程序的结束。</p>

<p>当script程序在运行的时候,在伪终端从设备之上终端行规程的所有输出都</p>

<p>被复制 </p>

<p>到一个script文件中(通常叫做typescript)。因为我们的击键通常被行规</p>

<p>程的模 </p>

<p>块回显,该script文件也包括了输入的内容。但是,因为口令字不被回显,</p>

<p>该scr </p>

<p>ipt文件不会包含口令字。 </p>

<p>图19.4 script程序 </p>

<p>本书中所有运行程序并显示其输出的实例都是由script程序实现的,这样避</p>

<p>免了手 </p>

<p>动拷贝程序输出可能带来的错误。 </p>

<p>在19.5节开发一个通用的pty程序后,我们将看到一个巧妙的shell程序能够</p>

<p>将它转 </p>

<p>化成一个script程序。 </p>

<p>expect程序 </p>

<p>伪终端可以用来使交互式的程序运行在非交互的状态中。许多程序需要一个终</p>

<p>端 </p>

<p>来运行,18.7节中的call进程就是一个例子。它假定标准输入是一个终端并</p>

<p>在启动 </p>

<p>时将其设置为初始模式(18.20程序)。该程序不能从一个shell程序中被运</p>

<p>行来自 </p>

<p>动拨号到远程系统,登录,取出信息和登出。 </p>

<p>同修改所有交互式程序来支持批处理模式的操作比较,一个更好的解决方法是</p>

<p>提 </p>

<p>供一种手段来通过一个script来驱动交互式程序。expect程序[Libes</p>

<p>1990;1991] </p>

<p>提供了这样的方法。类似于19.5节的pty程序,它使用伪终端来运行其他程</p>

<p>序。但 </p>

<p>是,expect还提供了一种编程语言用于检查程序的输出来确定以什么作为输</p>

<p>入发送 </p>

<p>给该程序。当一个交互式的程序开始从一个script运行时,我们不能仅仅是</p>

<p>将scr </p>

<p>ipt中的所有内容输入到程序中去。相应的,我们要通过检查程序的输出来决</p>

<p>定下 </p>

<p>一步输入的内容。 </p>

<p>运行协同进程 </p>

<p>在14.10的程序例子中,我们不能调用使用标准输入/输出库进行输入、输出</p>

<p>的协 </p>

<p>同进程,这是因为当我们通过管道与协同进程进行通讯时,标准输入/输出库</p>

<p>会将 </p>

<p>标准输入、输出的内容放到缓冲区中,从而引起死锁。如果协同进程是一个已</p>

<p>经编 </p>

<p>译的程序而我们又没有源程序,我们就无法在源程序中加入fflush语句来解</p>

<p>决这个 </p>

<p>问题。图14.9显示了一个进程驱动协同进程的情况。我们需要做的是将一个</p>

<p>伪终端 </p>

<p>放到两个进程之间,如图19.5所示。 </p>

<p>图19.5 用伪终端驱动一个协同进程 </p>

<p>现在协同进程的标准输入和标准输出就象一个终端设备一样,所以标准输入/</p>

<p>输出 </p>

<p>库会将这两个流设置为行缓冲的。 </p>

<p>父进程有两种不同的方法在自身和协同进程之间获得伪终端(这种情况下的父</p>

<p>进程 </p>

<p>可以象程序14.9,使用两个管道和协同进程进行通讯;或者象程序15.1那</p>

<p>样,使用 </p>

<p>一个流管道)。一个方法是父进程直接调用pty_fork函数(19.4节)而不是</p>

<p>fork。 </p>

<p>另一种方法是exec该pty程序,将协同进程作为参数(19.5节)。我们将在</p>

<p>说明pt </p>

<p>y程序后介绍这两种方法。 </p>

<p>观看长时间运行程序的输出 </p>

<p>使用任一个标准shell,我们都可以将一个需要长时间运行的程序放到后台运</p>

<p>行。 </p>

<p>但是如果我们将该程序的标准输出重定向到一个文件,并且如果它产生的输出</p>

<p>不多 </p>

<p>,我们就不能方便地监控程序的进展,这是因为标准的输入/输出库会将标准</p>

<p>输出 </p>

<p>放在缓冲区中保存。我们看到的将只是成块的输出结果,有时甚至可能是</p>

<p>8192字节 </p>

<p>一块。 </p>

<p>如果我们有源程序,我们可以加入fflush调用。另一种方法是,我们可以在</p>

<p>pty程 </p>

<p>序下运行该程序,让标准输入/输出库认为输出是终端。图19.6说明了这个结</p>

<p>构, </p>

<p>我们将这个缓慢输出的程序称为slowout。从登录shell到pty进程的</p>

<p>fort/exec箭头 </p>

<p>用虚线表示,以强调pty进程是作为后台任务运行的。 </p>

<p>19.3 打开伪终端设备 </p>

<p>在SVR4和4.3+BSD系统中打开伪终端设备的方法有所不同。我们提供两个函</p>

<p>数来处 </p>

<p>理所有细节:ptym_open用来打开下一个有效的伪终端主设备,ptys_open</p>

<p>用来打开 </p>

<p>相应的从设备。 </p>

<p>图19.6 使用伪终端运行一个缓慢输出的程序 </p>

<p>#include &quot;ourhdr.h&quot; </p>

<p>int ptym_open(char *pts_name); </p>

<p>返回:如果操作成功,返回伪终端主设备文件 </p>

<p>述符;否则返回-1 </p>

<p>int ptys_open(int fdm, char *pts_name); </p>

<p>返回:如果操作成功,返回伪终端从设备文件 </p>

<p>述符;否则返回-1 </p>

<p>通常我们不直接调用这两个函数--函数pty_fork(19.4节)调用它们并</p>

<p>fork出一个 </p>

<p>子进程。 </p>

<p>ptym_open决定下一个有效的伪终端主设备并打开该设备。这个调用必须分</p>

<p>配一个 </p>

<p>数组来存放主设备或从设备的名称,并且如果调用成功,相应的主设备或从设</p>

<p>备的 </p>

<p>名称会通过pts_name返回。这个名称和ptym_open返回的文件描述符将传给</p>

<p>ptys_o </p>

<p>pen,该函数用来打开一个从设备。 </p>

<p>在我们讲解pty_fork函数之后,使用两个函数来打开这两个设备的原因将会</p>

<p>很明显 </p>

<p>。通常,一个进程调用ptym_open来打开一个主设备并且得到从设备的名</p>

<p>称。该进 </p>

<p>程然后fork子进程,子进程在调用setid建立新的会话后调用ptys_open来</p>

<p>打开从设 </p>

<p>备。这就是从设备如何成为子进程的控制终端的过程。 </p>

<p>19.1.1 系统V的版本4 </p>

<p>所有在SVR4系统下的伪终端的流实现细节在AT&amp;T[1990d]的第十二章中有所</p>

<p>说明。 </p>

<p>三个函数在下列手册页中描述:grantpt(3),unlockpt(3),和</p>

<p>ptsname(3)。 </p>

<p>伪终端主设备是/dev/ptmx。这是一个流的增殖设备。这意味着当我们打开</p>

<p>该增殖 </p>

<p>设备,其open例程自动决定第一个未被使用的伪终端主设备并打开这个设</p>

<p>备。(在 </p>

<p>下一节我们将看到在Berkeley系统中,我们必须自己找到第一个未被使用的</p>

<p>伪终端 </p>

<p>主设备。) </p>

<p>_______________________________________________________________________</p>

<p>________ </p>

<p>#include &lt;sys/types.h&gt; </p>

<p>#include &lt;sys/stat.h&gt; </p>

<p>#include &lt;errno.h&gt; </p>

<p>#include &lt;fcntl.h&gt; </p>

<p>#include &lt;stropts.h&gt; </p>

<p>#include &quot;ourhdr.h&quot; </p>

<p>extern char *ptsname(int); /* prototype not in any system</p>

<p>header */ </p>

<p>int </p>

<p>ptym_open(char *pts_name) </p>

<p>{ </p>

<p>char *ptr; </p>

<p>int fdm; </p>

<p>strcpy(pts_name, &quot;/dev/ptmx&quot;); /* in case open fails */ </p>

<p>if ( (fdm = open(pts_name, O_RDWR)) &lt; 0) </p>

<p>return(-1); </p>

<p>if (grantpt(fdm) &lt; 0) { /* grant access to slave */ </p>

<p>close(fdm); </p>

<p>return(-2); </p>

<p>} </p>

<p>if (unlockpt(fdm) &lt; 0) { /* clear slave's lock flag */ </p>

<p>close(fdm); </p>

<p>return(-3); </p>

<p>} </p>

<p>if ( (ptr = ptsname(fdm)) == NULL) { /* get slave's name */ </p>

<p>close(fdm); </p>

<p>return(-4); </p>

<p>} </p>

<p>strcpy(pts_name, ptr); /* return name of slave */ </p>

<p>return(fdm); /* return fd of master */ </p>

<p>} </p>

<p>int </p>

<p>ptys_open(int fdm, char *pts_name) </p>

<p>{ </p>

<p>int fds; </p>

<p>/* following should allocate controlling terminal */ </p>

<p>if ( (fds = open(pts_name, O_RDWR)) &lt; 0) { </p>

<p>close(fdm); </p>

<p>return(-5); </p>

<p>} </p>

<p>if (ioctl(fds, I_PUSH, &quot;ptem&quot;) &lt; 0) { </p>

<p>close(fdm); </p>

<p>close(fds); </p>

<p>return(-6); </p>

<p>} </p>

<p>if (ioctl(fds, I_PUSH, &quot;ldterm&quot;) &lt; 0) { </p>

<p>close(fdm); </p>

<p>close(fds); </p>

<p>return(-7); </p>

<p>} </p>

<p>if (ioctl(fds, I_PUSH, &quot;ttcompat&quot;) &lt; 0) { </p>

<p>close(fdm); </p>

<p>close(fds); </p>

<p>return(-8); </p>

<p>} </p>

<p>return(fds); </p>

<p>} </p>

<p>_______________________________________________________________________</p>

<p>________ </p>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -