Farseerfc的小窩 - ncurses//farseerfc.me/2014-12-12T17:06:00+09:00從非緩衝輸入流到 Linux 控制檯的歷史2014-12-12T17:06:00+09:002014-12-12T17:06:00+09:00farseerfctag:farseerfc.me,2014-12-12:/from-unbuffered-stdin-to-history-of-linux-tty.html
<p>這篇也是源自於水源C板上板友的一個問題,涉及Linux上的控制檯的實現方式和歷史原因。因爲內容比較長,所以在這裏再排版一下發出來。
<a class="reference external" href="http://bbs.sjtu.edu.cn/bbstcon,board,C,reid,1418138991,file,M.1418138991.A.html">原帖在這裏</a> 。</p>
<div class="section" id="id2">
<h2><a class="toc-backref" href="#id7">可以設置不帶緩衝的標準輸入流嗎?</a></h2>
<p>WaterElement(UnChanged) 於 2014年12月09日23:29:51 星期二 問到:</p>
<blockquote>
<p>請問對於標準輸入流可以設置不帶緩衝嗎?比如以下程序</p>
<div class="highlight"><pre><span class="code-line"><span></span><span class="cp">#include</span> <span class="cpf"><stdio.h></span><span class="cp"></span></span>
<span class="code-line"><span class="cp">#include</span> <span class="cpf"><unistd.h></span><span class="cp"></span></span>
<span class="code-line"></span>
<span class="code-line"><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">argv</span><span class="p">[])</span> <span class="p">{</span></span>
<span class="code-line"> <span class="kt">FILE</span> <span class="o">*</span><span class="n">fp</span> <span class="o">=</span> <span class="n">fdopen</span><span class="p">(</span><span class="n">STDIN_FILENO</span><span class="p">,</span> <span class="s">"r"</span><span class="p">);</span></span>
<span class="code-line"> <span class="n">setvbuf</span><span class="p">(</span><span class="n">fp</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">_IONBF</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span></span>
<span class="code-line"> <span class="kt">char</span> <span class="n">buffer</span><span class="p">[</span><span class="mi">20</span><span class="p">];</span></span>
<span class="code-line"> <span class="n">buffer</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span></span>
<span class="code-line"> <span class="n">fgets</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="n">fp</span><span class="p">);</span></span>
<span class="code-line"> <span class="n">printf</span><span class="p">(</span><span class="s">"buffer …</span></span></pre></div></blockquote></div>
<p>這篇也是源自於水源C板上板友的一個問題,涉及Linux上的控制檯的實現方式和歷史原因。因爲內容比較長,所以在這裏再排版一下發出來。
<a class="reference external" href="http://bbs.sjtu.edu.cn/bbstcon,board,C,reid,1418138991,file,M.1418138991.A.html">原帖在這裏</a> 。</p>
<div class="section" id="id2">
<h2><a class="toc-backref" href="#id7">可以設置不帶緩衝的標準輸入流嗎?</a></h2>
<p>WaterElement(UnChanged) 於 2014年12月09日23:29:51 星期二 問到:</p>
<blockquote>
<p>請問對於標準輸入流可以設置不帶緩衝嗎?比如以下程序</p>
<div class="highlight"><pre><span class="code-line"><span></span><span class="cp">#include</span> <span class="cpf"><stdio.h></span><span class="cp"></span></span>
<span class="code-line"><span class="cp">#include</span> <span class="cpf"><unistd.h></span><span class="cp"></span></span>
<span class="code-line"></span>
<span class="code-line"><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">argv</span><span class="p">[])</span> <span class="p">{</span></span>
<span class="code-line"> <span class="kt">FILE</span> <span class="o">*</span><span class="n">fp</span> <span class="o">=</span> <span class="n">fdopen</span><span class="p">(</span><span class="n">STDIN_FILENO</span><span class="p">,</span> <span class="s">"r"</span><span class="p">);</span></span>
<span class="code-line"> <span class="n">setvbuf</span><span class="p">(</span><span class="n">fp</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">_IONBF</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span></span>
<span class="code-line"> <span class="kt">char</span> <span class="n">buffer</span><span class="p">[</span><span class="mi">20</span><span class="p">];</span></span>
<span class="code-line"> <span class="n">buffer</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span></span>
<span class="code-line"> <span class="n">fgets</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="n">fp</span><span class="p">);</span></span>
<span class="code-line"> <span class="n">printf</span><span class="p">(</span><span class="s">"buffer is:%s"</span><span class="p">,</span> <span class="n">buffer</span><span class="p">);</span></span>
<span class="code-line"> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span></span>
<span class="code-line"><span class="p">}</span></span>
</pre></div>
<p>似乎還是需要在命令行輸入後按回車纔會讓 <code class="code">
fgets</code>
返回,不帶緩衝究竟體現在哪裏?</p>
</blockquote>
<div class="section" id="id3">
<h3><a class="toc-backref" href="#id8">這和緩存無關,是控制檯的實現方式的問題。</a></h3>
<p>再講細節一點,這裏有很多個程序和設備。以下按 linux 的情況講:</p>
<ol class="arabic simple">
<li>終端模擬器窗口(比如xterm)收到鍵盤事件</li>
<li>終端模擬器(xterm)把鍵盤事件發給虛擬終端 pty1</li>
<li>pty1 檢查目前的輸入狀態,把鍵盤事件轉換成 stdin 的輸入,發給你的程序</li>
<li>你的程序的 c 庫從 stdin 讀入一個輸入,處理</li>
</ol>
<p>標準庫說的輸入緩存是在 4 的這一步進行的。而行輸入是在 3 的這一步被緩存起來的。</p>
<p>終端pty有多種狀態,一般控制檯程序所在的狀態叫「回顯行緩存」狀態,這個狀態的意思是:</p>
<ol class="arabic simple">
<li>所有普通字符的按鍵,會回顯到屏幕上,同時記錄在行緩存區裏。</li>
<li>處理退格( <kbd class="kbd">
BackSpace</kbd>
),刪除( <kbd class="kbd">
Delete</kbd>
)按鍵爲刪掉字符,左右按鍵移動光標。</li>
<li>收到回車的時候把整個一行的內容發給stdin。</li>
</ol>
<p>參考: <a class="reference external" href="http://en.wikipedia.org/wiki/Cooked_mode">http://en.wikipedia.org/wiki/Cooked_mode</a></p>
<p>同時在Linux/Unix下可以發特殊控制符號給pty讓它進入「raw」狀態,這種狀態下按鍵
不會被回顯,顯示什麼內容都靠你程序自己控制。
如果你想得到每一個按鍵事件需要用raw狀態,這需要自己控制回顯自己處理緩衝,
簡單點的方法是用 readline 這樣的庫(基本就是「回顯行緩存」的高級擴展,支持了
Home/End,支持歷史)或者 ncurses 這樣的庫(在raw狀態下實現了一個簡單的窗口/
事件處理框架)。</p>
<p>參考: <a class="reference external" href="http://en.wikipedia.org/wiki/POSIX_terminal_interface#History">http://en.wikipedia.org/wiki/POSIX_terminal_interface#History</a></p>
<p>除此之外, <kbd class="kbd">
Ctrl-C</kbd>
轉換到 SIGINT , <kbd class="kbd">
Ctrl-D</kbd>
轉換到 EOF 這種也是在 3 這一步做的。</p>
<p>以及,有些終端模擬器提供的 <kbd class="kbd">
Ctrl-Shift-C</kbd>
表示複製這種是在 2 這一步做的。</p>
<p>以上是 Linux/unix 的方式。 Windows的情況大體類似,只是細節上有很多地方不一樣:</p>
<ol class="arabic simple">
<li>窗口事件的接收者是創建 cmd 窗口的 Win32 子系統。</li>
<li>Win32子系統接收到事件之後,傳遞給位於 命令行子系統 的 cmd 程序</li>
<li>cmd 程序再傳遞給你的程序。</li>
</ol>
<p>Windows上同樣有類似行緩存模式和raw模式的區別,只不過實現細節不太一樣。</p>
</div>
<div class="section" id="strace">
<h3><a class="toc-backref" href="#id9">strace查看了下</a></h3>
<p>WaterElement(UnChanged) 於 2014年12月10日21:53:54 星期三 回復:</p>
<blockquote>
<p>感謝FC的詳盡解答。</p>
<p>用strace查看了下,設置標準輸入沒有緩存的話讀每個字符都會調用一次 <code class="code">
read</code>
系統調用,
比如輸入abc:</p>
<div class="highlight"><pre><span class="code-line"><span></span>read(0, abc</span>
<span class="code-line">"a", 1) = 1</span>
<span class="code-line">read(0, "b", 1) = 1</span>
<span class="code-line">read(0, "c", 1) = 1</span>
<span class="code-line">read(0, "\n", 1) = 1</span>
</pre></div>
<p>如果有緩存的話就只調用一次了 <code class="code">
read</code>
系統調用了:</p>
<div class="highlight"><pre><span class="code-line"><span></span>read(0, abc</span>
<span class="code-line">"abc\n", 1024) = 4</span>
</pre></div>
</blockquote>
</div>
<div class="section" id="raw-mode">
<h3><a class="toc-backref" href="#id10">如果想感受一下 raw mode</a></h3>
<p>沒錯,這個是你的進程內C庫做的緩存,tty屬於字符設備所以是一個一個字符塞給你的
程序的。</p>
<p>如果想感受一下 raw mode 可以試試下面這段程序(沒有檢測錯誤返回值)</p>
<div class="highlight"><pre><span class="code-line"><span></span><span class="cp">#include</span> <span class="cpf"><stdio.h></span><span class="cp"></span></span>
<span class="code-line"><span class="cp">#include</span> <span class="cpf"><unistd.h></span><span class="cp"></span></span>
<span class="code-line"><span class="cp">#include</span> <span class="cpf"><termios.h></span><span class="cp"></span></span>
<span class="code-line"></span>
<span class="code-line"><span class="k">static</span> <span class="kt">int</span> <span class="n">ttyfd</span> <span class="o">=</span> <span class="n">STDIN_FILENO</span><span class="p">;</span></span>
<span class="code-line"><span class="k">static</span> <span class="k">struct</span> <span class="n">termios</span> <span class="n">orig_termios</span><span class="p">;</span></span>
<span class="code-line"></span>
<span class="code-line"><span class="cm">/* reset tty - useful also for restoring the terminal when this process</span></span>
<span class="code-line"><span class="cm"> wishes to temporarily relinquish the tty</span></span>
<span class="code-line"><span class="cm">*/</span></span>
<span class="code-line"><span class="kt">int</span> <span class="nf">tty_reset</span><span class="p">(</span><span class="kt">void</span><span class="p">){</span></span>
<span class="code-line"> <span class="cm">/* flush and reset */</span></span>
<span class="code-line"> <span class="k">if</span> <span class="p">(</span><span class="n">tcsetattr</span><span class="p">(</span><span class="n">ttyfd</span><span class="p">,</span><span class="n">TCSAFLUSH</span><span class="p">,</span><span class="o">&</span><span class="n">orig_termios</span><span class="p">)</span> <span class="o"><</span> <span class="mi">0</span><span class="p">)</span> <span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span></span>
<span class="code-line"> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span></span>
<span class="code-line"><span class="p">}</span></span>
<span class="code-line"></span>
<span class="code-line"></span>
<span class="code-line"><span class="cm">/* put terminal in raw mode - see termio(7I) for modes */</span></span>
<span class="code-line"><span class="kt">void</span> <span class="nf">tty_raw</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span></span>
<span class="code-line"><span class="p">{</span></span>
<span class="code-line"> <span class="k">struct</span> <span class="n">termios</span> <span class="n">raw</span><span class="p">;</span></span>
<span class="code-line"></span>
<span class="code-line"> <span class="n">raw</span> <span class="o">=</span> <span class="n">orig_termios</span><span class="p">;</span> <span class="cm">/* copy original and then modify below */</span></span>
<span class="code-line"></span>
<span class="code-line"> <span class="cm">/* input modes - clear indicated ones giving: no break, no CR to NL,</span></span>
<span class="code-line"><span class="cm"> no parity check, no strip char, no start/stop output (sic) control */</span></span>
<span class="code-line"> <span class="n">raw</span><span class="p">.</span><span class="n">c_iflag</span> <span class="o">&=</span> <span class="o">~</span><span class="p">(</span><span class="n">BRKINT</span> <span class="o">|</span> <span class="n">ICRNL</span> <span class="o">|</span> <span class="n">INPCK</span> <span class="o">|</span> <span class="n">ISTRIP</span> <span class="o">|</span> <span class="n">IXON</span><span class="p">);</span></span>
<span class="code-line"></span>
<span class="code-line"> <span class="cm">/* output modes - clear giving: no post processing such as NL to CR+NL */</span></span>
<span class="code-line"> <span class="n">raw</span><span class="p">.</span><span class="n">c_oflag</span> <span class="o">&=</span> <span class="o">~</span><span class="p">(</span><span class="n">OPOST</span><span class="p">);</span></span>
<span class="code-line"></span>
<span class="code-line"> <span class="cm">/* control modes - set 8 bit chars */</span></span>
<span class="code-line"> <span class="n">raw</span><span class="p">.</span><span class="n">c_cflag</span> <span class="o">|=</span> <span class="p">(</span><span class="n">CS8</span><span class="p">);</span></span>
<span class="code-line"></span>
<span class="code-line"> <span class="cm">/* local modes - clear giving: echoing off, canonical off (no erase with</span></span>
<span class="code-line"><span class="cm"> backspace, ^U,...), no extended functions, no signal chars (^Z,^C) */</span></span>
<span class="code-line"> <span class="n">raw</span><span class="p">.</span><span class="n">c_lflag</span> <span class="o">&=</span> <span class="o">~</span><span class="p">(</span><span class="n">ECHO</span> <span class="o">|</span> <span class="n">ICANON</span> <span class="o">|</span> <span class="n">IEXTEN</span> <span class="o">|</span> <span class="n">ISIG</span><span class="p">);</span></span>
<span class="code-line"></span>
<span class="code-line"> <span class="cm">/* control chars - set return condition: min number of bytes and timer */</span></span>
<span class="code-line"> <span class="n">raw</span><span class="p">.</span><span class="n">c_cc</span><span class="p">[</span><span class="n">VMIN</span><span class="p">]</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span> <span class="n">raw</span><span class="p">.</span><span class="n">c_cc</span><span class="p">[</span><span class="n">VTIME</span><span class="p">]</span> <span class="o">=</span> <span class="mi">8</span><span class="p">;</span> <span class="cm">/* after 5 bytes or .8 seconds</span></span>
<span class="code-line"><span class="cm"> after first byte seen */</span></span>
<span class="code-line"> <span class="n">raw</span><span class="p">.</span><span class="n">c_cc</span><span class="p">[</span><span class="n">VMIN</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">raw</span><span class="p">.</span><span class="n">c_cc</span><span class="p">[</span><span class="n">VTIME</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="cm">/* immediate - anything */</span></span>
<span class="code-line"> <span class="n">raw</span><span class="p">.</span><span class="n">c_cc</span><span class="p">[</span><span class="n">VMIN</span><span class="p">]</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span> <span class="n">raw</span><span class="p">.</span><span class="n">c_cc</span><span class="p">[</span><span class="n">VTIME</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="cm">/* after two bytes, no timer */</span></span>
<span class="code-line"> <span class="n">raw</span><span class="p">.</span><span class="n">c_cc</span><span class="p">[</span><span class="n">VMIN</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">raw</span><span class="p">.</span><span class="n">c_cc</span><span class="p">[</span><span class="n">VTIME</span><span class="p">]</span> <span class="o">=</span> <span class="mi">8</span><span class="p">;</span> <span class="cm">/* after a byte or .8 seconds */</span></span>
<span class="code-line"></span>
<span class="code-line"> <span class="cm">/* put terminal in raw mode after flushing */</span></span>
<span class="code-line"> <span class="n">tcsetattr</span><span class="p">(</span><span class="n">ttyfd</span><span class="p">,</span><span class="n">TCSAFLUSH</span><span class="p">,</span><span class="o">&</span><span class="n">raw</span><span class="p">);</span></span>
<span class="code-line"><span class="p">}</span></span>
<span class="code-line"></span>
<span class="code-line"></span>
<span class="code-line"><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">argv</span><span class="p">[])</span> <span class="p">{</span></span>
<span class="code-line"> <span class="n">atexit</span><span class="p">(</span><span class="n">tty_reset</span><span class="p">);</span></span>
<span class="code-line"> <span class="n">tty_raw</span><span class="p">();</span></span>
<span class="code-line"> <span class="kt">FILE</span> <span class="o">*</span><span class="n">fp</span> <span class="o">=</span> <span class="n">fdopen</span><span class="p">(</span><span class="n">ttyfd</span><span class="p">,</span> <span class="s">"r"</span><span class="p">);</span></span>
<span class="code-line"> <span class="n">setvbuf</span><span class="p">(</span><span class="n">fp</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">_IONBF</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span></span>
<span class="code-line"> <span class="kt">char</span> <span class="n">buffer</span><span class="p">[</span><span class="mi">20</span><span class="p">];</span></span>
<span class="code-line"> <span class="n">buffer</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span></span>
<span class="code-line"> <span class="n">fgets</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="n">fp</span><span class="p">);</span></span>
<span class="code-line"> <span class="n">printf</span><span class="p">(</span><span class="s">"buffer is:%s"</span><span class="p">,</span> <span class="n">buffer</span><span class="p">);</span></span>
<span class="code-line"> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span></span>
<span class="code-line"><span class="p">}</span></span>
</pre></div>
</div>
</div>
<div class="section" id="id4">
<h2><a class="toc-backref" href="#id11">終端上的字符編程</a></h2>
<p>vander(大青蛙) 於 2014年12月12日08:52:20 星期五 問到:</p>
<blockquote>
<p>學習了!</p>
<p>進一步想請教一下fc大神。如果我在Linux上做終端上的字符編程,是否除了用ncurses庫
之外,也可以不用該庫而直接與終端打交道,就是你所說的直接在raw模式?
另外,終端類型vt100和linux的差別在哪裏?爲什麼Kevin Boone的KBox配置手冊裏面說必
須把終端類型設成linux,而且要加上terminfo文件,才能讓終端上的vim正常工作?term
info文件又是幹什麼的?</p>
</blockquote>
<div class="section" id="id5">
<h3><a class="toc-backref" href="#id12">Linux控制檯的歷史</a></h3>
<p>嗯理論上可以不用 ncurses 庫直接在 raw 模式操縱終端。</p>
<p>這裏稍微聊一下terminfo/termcap的歷史,詳細的歷史和吐槽參考
<a class="reference external" href="http://web.mit.edu/~simsong/www/ugh.pdf">Unix hater's Handbook</a>
第6章 Terminal Insanity。</p>
<p>首先一個真正意義上的終端就是一個輸入設備(通常是鍵盤)加上一個輸出設備(打印
機或者顯示器)。很顯然不同的終端的能力不同,比如如果輸出設備是打印機的話,顯
示出來的字符就不能刪掉了(但是能覆蓋),而且輸出了一行之後就不能回到那一行了
。再比如顯示器終端有的支持粗體和下劃線,有的支持顏色,而有的什麼都不支持。
早期Unix工作在電傳打字機(TeleTYpe)終端上,後來Unix被port到越來越多的機器上
,然後越來越多類型的終端會被連到Unix上,很可能同一臺Unix主機連了多個不同類型
的終端。由於是不同廠商提供的不同的終端,能力各有不同,自然控制他們工作的方式
也是不一樣的。所有終端都支持回顯行編輯模式,所以一般的面向行的程序還比較好寫
,但是那時候要撰寫支持所有終端的「全屏」程序就非常痛苦,這種情況就像現在瀏覽
器沒有統一標準下寫HTML要測試各種瀏覽器兼容性一樣。
通常的做法是</p>
<ol class="arabic simple">
<li>使用最小功能子集</li>
<li>假設終端是某個特殊設備,不管別的設備。</li>
</ol>
<p>水源的代碼源頭 Firebird2000 就是那樣的一個程序,只支持固定大小的vt102終端。</p>
<p>這時有一個劃時代意義的程序出現了,就是 vi,試圖要做到「全屏可視化編輯」。這在
現在看起來很簡單,但是在當時基本是天方夜譚。
vi 的做法是提出一層抽象,記錄它所需要的所有終端操作,然後有一個終端類型數據庫
,把那些操作映射到終端類型的具體指令上。當然並不是所有操作在所有終端類型上都
支持,所以會有一堆 fallback,比如要「強調」某段文字,在彩色終端上可能
fallback 到紅色,在黑白終端上可能 fallback 到粗體。</p>
<p>vi 一出現大家都覺得好頂讚,然後想要寫更多類似 vi 這樣的全屏程序。然後 vi 的作
者就把終端抽象的這部分數據庫放出來形成一個單獨的項目,叫 termcap (Terminal
Capibility),對應的描述終端的數據庫就是 termcap 格式。然後 termcap 只是一個
數據庫(所以無狀態)還不夠方便易用,所以後來又有人用 termcap 實現了 curses 。</p>
<p>再後來大家用 curses/termcap 的時候漸漸發現這個數據庫有一點不足:它是爲 vi 設
計的,所以只實現了 vi 需要的那部分終端能力。然後對它改進的努力就形成了新的
terminfo 數據庫和 pcurses 和後來的 ncurses 。 然後 VIM 出現了自然也用
terminfo 實現這部分終端操作。</p>
<p>然後麼就是 X 出現了, xterm 出現了,大家都用顯示器了,然後 xterm 爲了兼容各種
老程序加入了各種老終端的模擬模式。不過因爲最普及的終端是 vt100 所以 xterm 默
認是工作在兼容 vt100 的模式下。然後接下來各種新程序(偷懶不用*curses的那些)
都以 xterm/vt100 的方式寫。</p>
<p>嗯到此爲止是 Unix 世界的黑歷史。</p>
<p>知道這段歷史的話就可以明白爲什麼需要 TERM 變量配合 terminfo 數據庫纔能用一些
Unix 下的全屏程序了。類比一下的話這就是現代瀏覽器的 user-agent。</p>
<p>然後話題回到 Linux 。 大家知道 Linux 早期代碼不是一個 OS, 而是 Linus 大神想
在他的嶄新蹭亮的 386-PC 上遠程登錄他學校的 Unix 主機,接收郵件和逛水源(咳咳
)。於是 Linux 最早的那部分代碼並不是一個通用 OS 而只是一個 bootloader 加一個
終端模擬器。所以現在 Linux 內核裏還留有他當年實現的終端模擬器的部分代碼,而這
個終端模擬器的終端類型就是 linux 啦。然後他當時是爲了逛水源嘛所以 linux 終端
基本上是 vt102 的一個接近完整子集。</p>
<p>說到這裏脈絡大概應該清晰了, xterm終端類型基本模擬 vt100,linux終端類型基本模
擬 vt102。這兩個的區別其實很細微,都是同一個廠商的兩代產品嘛。有差別的地方差
不多就是 <kbd class="kbd">
Home</kbd>
/ <kbd class="kbd">
End</kbd>
/ <kbd class="kbd">
PageUp</kbd>
/ <kbd class="kbd">
PageDown</kbd>
/ <kbd class="kbd">
Delete</kbd>
這些不在 ASCII 控制字符表裏的按鍵的映射關係不同。</p>
<p>嗯這也就解釋了爲什麼在linux環境的圖形界面的終端裏 telnet 上水源的話,上面這些
按鍵會錯亂…… 如果設置終端類型是 linux/vt102 的話就不會亂了。在 linux 的
TTY 裏 telnet 也不會亂的樣子。</p>
<p>寫到這裏纔發現貌似有點長…… 總之可以參考
<a class="reference external" href="http://web.mit.edu/~simsong/www/ugh.pdf">Unix hater's Handbook</a>
裏的相關歷史評論和吐槽,那一段非常有意思。</p>
</div>
</div>