?? (ldd) ch05-字符設備驅動程序的擴展操作.htm
字號:
color=#ffffff>關于</FONT></A></DIV></TD>
<TD width="1%" height=4>
<DIV align=center><FONT color=#ffffff>|</FONT></DIV></TD>
<TD width="8%" height=4>
<DIV align=center><A href="mailto:joyfire@sina.com"><FONT
color=#ffffff>聯系</FONT></A></DIV></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE>
<TABLE borderColor=#666666 cellPadding=2 width="90%" align=center border=2>
<TBODY>
<TR>
<TD bgColor=#000000>
<P align=center><A
href="http://211.71.69.201/joyfire/lsdp/index.htm"><FONT color=#ffffff
size=2>目錄頁</FONT></A> | <A
href="http://211.71.69.201/joyfire/lsdp/6.htm"><FONT color=#ffffff
size=2>上一頁</FONT></A> | <A
href="http://211.71.69.201/joyfire/lsdp/8.htm"><FONT color=#ffffff
size=2>下一頁</FONT></A></P>
<P align=center><FONT face=黑體 color=#ffffff size=6>(LDD)
Ch05-字符設備驅動程序的擴展操作</FONT></P><SPAN
style="LINE-HEIGHT: 1; LETTER-SPACING: 0pt"><FONT color=#ffffff size=3>
<P>發信人: Altmayer (alt), 信區: GNULinux<BR>標 題: (LDD) Ch05-字符設備驅動程序的擴展操作(轉載)<BR>發信站: 飲水思源 (2001年12月13日08:57:16 星期四), 站內信件<BR> <BR>【 以下文字轉載自 <FONT
color=#00ff00>UNIXpost </FONT>討論區 】<BR>【 原文由<FONT
color=#00ff00> altmayer.bbs@bbs.nju.edu.cn,</FONT> 所發表 】<BR> <BR>【 以下文字轉載自 <FONT
color=#00ff00>altmayer </FONT>的信箱 】<BR>第5章 字符設備驅動程序的擴展操作<BR> <BR> <BR>在關于字符設備驅動程序的那一章中,我們構建了一個完整的設備驅動程序,從中用戶<BR>可以讀也可以寫。但實際一個驅動程序通常會提供比同步read和write更多的功能。現在<BR>如果出了什么毛病,我已經配備了調試工具,我們可以大膽的實驗并實現新操作。<BR> <BR>通過補充設備讀寫操作的功能之一就是控制硬件,最常用的通過設備驅動程序完成控制<BR>動作的方法就是實現ioctl方法。另一種方法是檢查寫到設備中的數據流,使用特殊序列<BR>做為控制命令。盡管有時也使用后者,但應該盡量避免這樣使用。不過稍后我們還是會<BR>在本章的“非ioctl設備控制”一節中介紹這項技術。<BR> <BR>正如我在前一章中所猜想的,ioctl系統調用為驅動程序執行“命令”提供了一個設備相<BR>關的入口點。與read和其他方法不同,ioctl是設備相關的,它允許應用程序訪問被驅動<BR>硬件的特殊功能――配置設備以及進入或退出操作模式。這些“控制操作”通常無法通<BR></P></FONT><FONT
color=#ffffff size=3>
<P>硬件的特殊功能――配置設備以及進入或退出操作模式。這些“控制操作”通常無法通<BR>過read/write文件操作完成。例如,你向串口寫的所有數據都通過串口發送出去了,你<BR>無法通過寫設備改變波特率。這就是ioctl所要做的:控制I/O通道。<BR> <BR>實際設備(與scull不同)的另一個重要功能是,讀或寫的數據需要同其他硬件交互,需<BR>要某些同步機制。阻塞型I/O和異步觸發的概念將滿足這些需求,本章將通過一個改寫的<BR>scull設備介紹這些內容。驅動程序利用不同進程間的交互產生異步事件。與最初的scul<BR>l相同,你無需特殊硬件來測試驅動程序是否可以工作。直到第8章“硬件管理”我才會<BR>真正去與硬件打交道。<BR> <BR>ioctl<BR>在用戶空間內調用ioctl函數的原型大致如下:<BR> <BR>(代碼)<BR> <BR>由于使用了一連串的“.”的緣故,該原型在Unix系統調用列表之中非常突出,這些點代<BR>表可變數目參數。但是在實際系統中,系統調用實際上不會有可變數目個參數。因為用<BR>戶程序只能通過第2章“編寫和運行模塊”的“用戶空間和內核空間”一節中介紹的硬件<BR>“門”才能訪問內核,系統調用必須有精確定義的參數個數。因此,ioctl的第3個參數<BR>事實上只是一個可選參數,這里用點只是為了在編譯時防止編譯器進行類型檢查。第3個<BR>參數的具體情況與要完成的控制命令(第2個參數)有關。某些命令不需要參數,某些需<BR>要一個整數做參數,而某些則需要一個指針做參數。使用指針通常是可以用來向ioctl傳<BR>遞任意數目數據;設備可以從用戶空間接收任意大小的數據。<BR></P></FONT><FONT
color=#ffffff size=3>
<P>遞任意數目數據;設備可以從用戶空間接收任意大小的數據。<BR> <BR>系統調用的參數根據方法的聲明傳遞給驅動程序方法:<BR> <BR>(代碼)<BR> <BR>inode和filp指針是根據應用程序傳遞的文件描述符fd計算而得的,與read和write的用<BR>法一致。參數cmd不經修改地傳遞給驅動程序,可選的arg參數無論是指針還是整數值,<BR>它都以unsigned long的形式傳遞給驅動程序。如果調用程序沒有傳遞第3個參數,驅動<BR>程序所接收的arg沒有任何意義。<BR> <BR>由于附加參數的類型檢查被關閉了,如果非法參數傳遞給ioctl,編譯器無法向你報警,<BR>程序員在運行前是無法注意這個錯誤的。這是我所見到的ioctl語義方面的唯一一個問題<BR>。<BR> <BR>如你所想,大多數ioctl實現都包括一個switch語句來根據cmd參數選擇正確的操作。不<BR>同的命令對應不同的數值,為了簡化代碼我們通常會使用符號名代替數值。這些符號名<BR>都是在預處理中賦值的。不同的驅動程序通常會在它們的頭文件中聲明這些符號;scull<BR>就在scull.h中聲明了這些符號。<BR> <BR>選擇ioctl命令<BR>在編寫ioctl代碼之前,你需要選擇對應不同命令的命令號。遺憾的是,簡單地從1開始<BR>選擇號碼是不能奏效的。<BR></P></FONT><FONT
color=#ffffff size=3>
<P>選擇號碼是不能奏效的。<BR> <BR>為了防止對錯誤的設備使用正確的命令,命令號應該在系統范圍內是唯一的。這種失配<BR>并不是不很容易發生,程序可能發現自己正在對象FIFO和kmouse這類非串口輸入流修改<BR>波特率。如果每一個ioctl命令都是唯一的,應用程序就會獲得一個EINVAL錯誤,而不是<BR>無意間成功地完成了操作。<BR> <BR>為了達到唯一性的目的,每一個命令號都應該由多個位字段組成。Linux的第一版使用了<BR>一個16位整數:高8位是與設備相關的“幻”數,低8位是一個序列號碼,在設備內是唯<BR>一的。這是因為,用Linus的話說,他有點“無頭緒”,后來才接收了一個更好的位字段<BR>分割方案。遺憾的是,很少有驅動程序使用新的約定,這就挫傷了程序員使用新約定的<BR>熱情。在我的源碼中,為了發掘這種約定都提供了那些功能,同時防止被其他開發人員<BR>當成異教徒而禁止,我使用了新的定義命令的方法。<BR> <BR>為了給我的驅動程序選擇ioctl號,你應該首先看看include/asm/ioctl.h和Documentati<BR>on/ioctl-number.txt這兩個文件。頭文件定義了位字段:類型(幻數),基數,傳送方<BR>向,參數的尺寸等等。ioctl-number.txt文件中羅列了在內核中使用的幻數。這個文件<BR>的新版本(2.0以及后繼內核)也給出了為什么應該使用這個約定的原因。<BR> <BR>很不幸,在1.2.x中發行的頭文件沒有給出切分ioctl位字段宏的全集。如果你需要象我<BR>的scull一樣使用這種新方法,同時還要保持向后兼容性,你使用scull/sysdep.h中的若<BR>干代碼行,我在那里給出了解決問題的文檔的代碼。<BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR>現在已經不贊成使用的選擇ioctl號碼的舊方法非常簡單:選擇一個8位幻數,比如“k”<BR>(十六進制為0x6b),然后加上一個基數,就象這樣:<BR> <BR>(代碼)<BR> <BR>如果應用程序和驅動程序都使用了相同的號碼,你只要在驅動程序里實現switch語句就<BR>可以了。但是,這種在傳統Unix中有基礎的定義ioctl號碼的方法,不應該再在新約定中<BR>使用。這里我介紹就方法只是想給你看看一個ioctl號碼大致是個什么樣子的。<BR> <BR>新的定義號碼的方法使用了4個位字段,它們有如下意義。下面我所介紹的新符號都定義<BR>在<linux/ioctl.h>中。<BR> <BR>類型<BR> <BR>幻數。選擇一個號碼,并在整個驅動程序中使用這個號碼。這個字段有8位寬(_IOC_TYP<BR>EBITS)。<BR> <BR>號碼<BR> <BR>基(序列)數。它也是8位寬(_IOC_NRBITS)。<BR> <BR>方向<BR></P></FONT><FONT
color=#ffffff size=3>
<P>方向<BR> <BR>如果該命令有數據傳輸,它定義數據傳輸的方向。可以使用的值有,_IOC_NONE(沒有數<BR>據傳輸),_IOC_READ,_IOC_WRITE和_IOC_READ | _IOC_WRITE(雙向傳輸數據)。數據<BR>傳輸是從應用程序的角度看的;IOC_READ意味著從設備中讀數據,驅動程序必須向用戶<BR>空間寫數據。注意,該字段是一個位屏蔽碼,因此可以用邏輯AND操作從中分解出_IOC_R<BR>EAD和_IOC_WRITE。<BR> <BR>尺寸<BR> <BR>所涉及的數據大小。這個字段的寬度與體系結構有關,當前的范圍從8位到14位不等。你<BR>可以在宏_IOC_SIZEBITS中找到某種體系結構的具體數值。不過,如果你想要你的驅動程<BR>序可移植,你只能認為最大尺寸可達255個字節。系統并不強制你使用這個字段。如果你<BR>需要更大尺度的數據傳輸,你可以忽略這個字段。下面我們將介紹如何使用這個字段。<BR> <BR>包含在<linux/ioctl.h>之中的頭文件<asm/ioctl.h>定義了可以用來構造命令號碼的宏<BR>:_IO(type,nr),_IOR(type,nr,size),_IOW(type,nr,size)和IOWR(type,nr,size)。<BR>每一個宏都對應一種可能的數據傳輸方向,其他字段通過參數傳遞。頭文件還定義了解<BR>碼宏:_IOC_DIR(nr),_IOC_TYPE(nr),_IOC_NR(nr)和_IOC_SIZE(nr)。我不打算詳細介<BR>紹這些宏,頭文件里的定義已經足夠清楚了,本節稍后會給出樣例。<BR> <BR>這里是scull中如果定義ioctl命令的。特別地,這些命令設置并獲取驅動程序的配置參<BR>數。在標準的宏定義中,要傳送的數據項的尺寸有數據項自身的實例代表,而不是sizeo<BR></P></FONT><FONT
color=#ffffff size=3>
<P>數。在標準的宏定義中,要傳送的數據項的尺寸有數據項自身的實例代表,而不是sizeo<BR>f(item),這是因為sizeof是宏擴展后的一部分。<BR> <BR>(代碼)<BR> <BR>最后一條命令,HARDRESET,用來將模塊使用計數器復位為0,這樣就可以在計數器發生<BR>錯誤時就可以卸載模塊了。實際的源碼定義了從IOCHQSET到HARDRESET間的所有命令,但<BR>這里沒有列出。<BR> <BR>我選擇用兩種方法實現整數參數傳 莰D―通過指針和顯式數值,盡管根據已有的約定,i<BR>octl應該使用指針完成數據交換。同樣,這兩種方法還用于返回整數:通過指針和設置<BR>返回值。如果返回值是正的,這就可以工作;對與任何一個系統調用的返回值,正值是<BR>受保護的(如我們在read和write所見到的),而負值則被認為是一個錯誤值,用其設置<BR>用戶空間中的errno變量。<BR> <BR>“交換”和“移位”操作并不專用于scull設備。我實現“交換”操作是為了給出“方向<BR>”字段的所有可能值,而“移位”操作則將“告知”和“查詢”操作組合在一起。某些<BR>時候是需要原子性*測試兼設置這類操作的――特別是當應用程序需要加鎖和解鎖時。<BR> <BR>顯式的命令基數沒有什么特殊意義。它只是用來區分命令的。事實上,由于ioctl號碼的<BR>“方向”為會有所不同,你甚至可以在讀命令和寫命令中使用同一個基數。我選擇除了<BR>在聲明中使用基數外,其他地方都不使用它,這樣我就不必為符號值賦值了。這也是為<BR>什么顯式的號碼出現在上面的定義中。我只是向你介紹一種使用命令號碼的方法,你可<BR></P></FONT><FONT
color=#ffffff size=3>
<P>什么顯式的號碼出現在上面的定義中。我只是向你介紹一種使用命令號碼的方法,你可<BR>以自由地采用不同的方法使用它。<BR> <BR>當前,參數cmd的值內核并沒有使用,而且以后也不可能使用。因此,如果你想偷懶,你<BR>可以省去上面那些復雜的聲明,而直接顯式地使用一組16位數值。但另一方面,如果你<BR>這樣做了,你就無法從使用位字段中受益了。頭文件<linux/kd.h>就是這種舊風格方法<BR>的例子,但是它們并不是因為偷懶才這樣做的。修改這個文件需要重新編譯許多應用程<BR>序。<BR> <BR>返回值<BR>ioctl的實現通常就是根據命令號碼的一個switch語句。但是,當命令號碼不能匹配任何<BR>一個合法操作時,default選擇使用是什么?這個問題是很有爭議性的。大多數內核函數<BR>返回-EINVAL(“非法參數”),這是由于命令參數確實不是一個合法的參數,這樣做是<BR>合適的。然而,POSIX標準上說,如果調用了一個不合適的ioctl命令,應該返回-ENOTTY<BR>。對應的消息是“不是終端”――這不是用戶所期望的。你不得不決定是嚴格依從標準<BR>還是一般常識。我們將本章的后面介紹為什么依從POSIX標準需要返回ENOTTY。<BR> <BR>預定義命令<BR>盡管ioctl系統調用大部分都用于操作設備,但還有一些命令是由內核識別的。注意,這<BR>些命令是在你自己的文件操作前調用的,所以如果你選擇了和它們相同的命令號碼,你<BR>將無法接收到那個命令的請求,而且由于ioctl命令的不唯一性,應用程序會請求一些未<BR>可知的請求。<BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR>預定義命令分為3組:用于任何文件(普通,設備,FIFO和套接字文件)的,僅用于普通<BR>文件的以及和文件系統相關的;最后一組命令只能在宿主機文件系統上執行(間chattr<BR>命令)。設備驅動程序編寫者僅對第1組感興趣就可以了,它們的幻數是“T”。分析其<BR>他組的工作將留做讀者的練習;ext2_ioctl是其中最有意思的函數(盡管比你想象的要<BR>容易得多),它實現了只追加標志和不可變標志。<BR> <BR>下列ioctl命令對任何文件都是預定義的:<BR> <BR>FIOCLEX<BR> <BR>設置exec時關閉標志(File IOctl Close on EXec)。<BR> <BR>FIONCLEX<BR> <BR>清除exec時關閉標志。<BR> <BR>FIOASYNC<BR> <BR>設置或復位文件的同步寫。Linux中沒有實現同步寫;但這個調用存在,這樣請求同步寫<BR>的應用程序就可以編譯和運行了。如果你不知道同步寫是怎么回事,你也不用費神去了<BR>解它了:你不需要它。<BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR>FIONBIO<BR> <BR>“File IOctl Nonblocking I/O(文件ioctl非阻塞型I/O)”(稍后在“阻塞型和非阻<BR>塞型操作”一節中介紹)。該調用修改filp->f_flags中的O_NONBLOCK標志。傳遞給系統<BR>調用的第3個參數用來表明該標志是設置還是清除。我們將在本章后面談到它的作用。注<BR>意,fcntl系統調用使用F_SETFL命令也可以修改這個標志。<BR> <BR>列表中的最后一項引入了一個新系統調用fcntl,它看起來和ioctl很象。事實上,fcntl<BR>調用與ioctl非常相似,它也有一個命令參數和額外(可選的)一個參數。它和ioctl分<BR>開主要是由于歷史原因:當Unix開發人員面對“控制”I/O操作的問題時,他們決定文件<BR>和設備應該是不同的。那時,唯一的設備是終端,這也就解釋了為什么-ENOTTY是標準的<BR>非法ioctl命令的返回值。這個問題是是否保持向后兼容性的老問題。<BR> <BR>使用ioctl參數<BR>我們需要講解的最后一點是,在分析scull驅動程序的ioctl代碼前,首先弄明白如何使<BR>用那個額外的參數。如果它是一個整數就非常簡單了:可以直接使用它。如果它是一個<BR>指針,就必須注意一些問題了。<BR> <BR>當用一個指針引用用戶空間時,我們首先要確保它指向了合法的用戶空間,并且對應頁<BR>面當前恰在映射中。如果內核代碼試圖訪問范圍之外的地址,處理器就會發出一個異常<BR>。內核代碼中的異常將由上至2.0.x的內核轉換為oops消息。設備驅動程序應該通過驗證<BR>將要訪問的用戶地址空間的合法性來防止這種失效的發生,如果地址是非法的應該返回<BR></P></FONT><FONT
color=#ffffff size=3>
<P>將要訪問的用戶地址空間的合法性來防止這種失效的發生,如果地址是非法的應該返回<BR>一個錯誤碼。<BR> <BR>Linux 2.1中引入新功能之一就是內核代碼的異常處理。遺憾的是,正確的實現需要驅動<BR>程序-內核接口的較大改動。本章給出的非法只適用于舊內核,從1.2.13到2.0.x。新接<BR>口將在第17章“近期發展”的“處理內核空間失效”一節中介紹,那里給出的例子通過<BR>某些預處理宏將使支持的內核擴展到2.1.43。<BR> <BR>內核1.x.y和2.0.x的地址驗證是通過函數verify_area實現的,它的原型定義在<linux/m<BR>m.h>中:<BR> <BR>(代碼)<BR> <BR>第一個參數應該是VERIFY_READ或VERIFY_WRITE,這取決于你要在內存區上完成讀還是寫<BR>操作。ptr參數是一個用戶空間地址,extent是一個字節計數。例如,如果ioctl需要從<BR>用戶空間讀一個整數,extent就是sizeof(int)。如果在指定的地址上進行讀和寫操作,<BR>使用VERIFY_WRITE,它是VERIFY_READ的超集。<BR> <BR>驗證讀只檢查地址是否是合法的:除此之外,驗證寫要好檢查只讀和copy-on-write頁面<BR>。copy-on-write頁面一個共享可寫頁面,它還沒有被任何共享進程寫過;當你驗證寫時<BR>,verify_area完成“復制兼完成可寫配置”操作。很有意思的是,這里無需檢查頁面是<BR>否“在”內存中,這是由于合法頁面將由失效函數正確地進行處理,甚至從內核代碼中<BR>調用也可以。我們已經在第3章“字符設備”的“Scull的內存使用”一節中看到內核代<BR></P></FONT><FONT
color=#ffffff size=3>
<P>調用也可以。我們已經在第3章“字符設備”的“Scull的內存使用”一節中看到內核代<BR>碼可以成功地完成頁面失效處理。<BR> <BR>象大多數函數一樣,verify_area返回一個整數值:0意味著成功,負值代表一個錯誤,<BR>應該將這個錯誤返回給調用者。<BR> <BR>scull源碼在switch之前分析ioctl號碼的各個位字段:<BR> <BR>(代碼)<BR> <BR>在調用verify_area之后,再有驅動程序完成真正的數據傳送。除了memcpy_tofs和memcp<BR>y_fromfs函數外,程序員還可以使用兩個專為常用數據尺寸(1,2和4個字節,在以及64<BR>位平臺上的8個字節)優化的函數。這些函數定義在<asm/segment.h>中。<BR> <BR>put_user(datum, ptr)<BR> <BR>實際上它是一個最終調用__put_user的宏;編譯時將其擴展為一條機器指令。驅動程序<BR>應該盡可能使用put_user,而不是memcpy_tofs。由于在宏表達式中不進行類型檢查,你<BR>可以傳遞給put_user任何類型的數據指針,不過它應該是一個用戶空間地址。數據傳輸<BR>的尺寸依賴于ptr參數的類型,這是在編譯時通過特殊的gcc偽函數實現的,這里沒有介<BR>紹的必要。結果,如果ptr是一個字符指針,就傳遞1個字節,依此類推分別有2,4和8個<BR>字節。如果被指引的數據不是所支持的尺寸,被編譯的代碼就會調用函數bad_user_acce<BR>ss_length。如果這些編譯代碼是一個模塊,由于這個符號沒有開放,模塊就不能加載了<BR></P></FONT><FONT
color=#ffffff size=3>
<P>ss_length。如果這些編譯代碼是一個模塊,由于這個符號沒有開放,模塊就不能加載了<BR>。<BR> <BR>get_user(ptr)<BR> <BR>這個宏用來從用戶空間獲取一個數據。除了數據傳輸的方向不同外,它與put_user是一<BR>樣的。<BR> <BR>當insmod不能解析符號時,bad_user_access_length的又臭又長的名字可以當作一個很<BR>有意義的錯誤信息。這樣,開發人員就可以在向大眾分布模塊前加載和測試模塊,他會<BR>很快找到并修改錯誤。相反,如果使用了不正確尺寸的put_user和get_user直接編譯到<BR>了內核中,bad_user_access_length就會導致系統panic。盡管對于尺寸錯誤的數據傳輸<BR>來說,oops比其系統panic要友好得多,但還是選擇了較為激進的方法來盡力杜絕這種錯<BR>誤。<BR> <BR>scull的ioctl實現只傳送設備的可配置參數,其代碼非常簡單,羅列如下:<BR> <BR>(代碼)<BR> <BR>還有6項是操作scull_qset的。這些操作scull_quantum的一樣,為了節省空間,沒有在<BR>上面的例子中列出。<BR> <BR>從調用者的角度看(即從用戶空間),傳遞和接收參數的6種方法如下所示:<BR></P></FONT><FONT
color=#ffffff size=3>
<P>從調用者的角度看(即從用戶空間),傳遞和接收參數的6種方法如下所示:<BR> <BR>(代碼)<BR> <BR>如果你需要寫一個可以在Linux 1.2里運行的模塊,get_user和put_user會是非常棘手的<BR>函數,因為它們直到內核1.3才引入到系統中。在切換到類型依賴宏之前,程序員使用一<BR>些稱為get_user_byte等等的函數。舊的宏只在內核1.3中定義了,在2.0內核中,只有你<BR>事先使用了#define WE_REALLY_WANT_TO_USE_A_BROKEN_INTERFACE時才能使用舊的宏。<BR>不過為了可移植性,為舊內核定義put_user是一種更好的解決方法,于是為了驅動程序<BR>可以在舊內核中良好運行,scull/sydep.h包含了這些宏的定義。<BR> <BR>非ioctl設備控制<BR>有時通過向設備自身發送寫序列能夠更好地完成對設備的控制。例如,這一技術使用在<BR>控制臺驅動程序中,它稱為“escape序列”,用來控制光標移動,改變默認顏色,或是<BR>完成某些配置任務。用這種方法實現設備控制的好處是,用戶僅用寫數據就可以完成對<BR>設備的控制,無需使用(有時是寫)完成設備配置的程序。<BR> <BR>例如,程序setterm通過打印escape序列完成對控制臺(或其他終端)的配置。這種方法<BR>的優點是可以遠程控制設備。由于可以簡單地重定向數據流完成配置工作,控制程序可<BR>以運行在另外一臺不同的計算機上,而不一定非要在被控設備的計算機上。你已經在終<BR>端上使用了這項技術,但這項技術可以更通用一些。<BR> <BR>“通過打印控制”的缺點是,它給設備增加了策略限制;例如,只有你確認控制序列不<BR></P></FONT><FONT
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -