天天看點

嵌入式驅動解析:從序列槽驅動到Linux驅動模型

本文通過對Linux下序列槽驅動的分析。由最上層的C庫。到作業系統系統調用層的封裝。再到tty子系統的核心。再到一系列線路規程。再到最底層的硬體操作。

對Linux中的tty子系統進行簡要的說明。從理論到實踐。以便讀者能對OS原理有更深入的了解和更具體的掌握。

在具體分析之前。我們必須對序列槽。驅動。和Linux作業系統有一定的了解。這一階段我們有三個問題需要解決:

1.什麼是Linux作業系統。

2.什麼是Linux裝置驅動。

3.關于序列槽的種種。

要了解這些概念。如下我介紹了一點這方面的知識。不過遺憾的是對一些概念有着不可避免的向前引用。

這個過程中我會盡量忽略次要因素。以在本次調研中最主要目的為主線。如果讀者您對這些概念已經有很深入的了解。可以直接閱讀後面的代碼分析:

1、什麼是Linux作業系統 ?

Linux是一套免費使用和自由傳播的類Unix作業系統,是一個基于POSIX和UNIX的多使用者、多任務、支援多線程和多CPU的作業系統。

它能運作主要的UNIX工具軟體、應用程式和網絡協定。它支援32位和64位硬體。Linux繼承了Unix以網絡為核心的設計思想,是一個性能穩定的多使用者網絡作業系統。

Linux作業系統誕生于1991 年10 月5 日(這是第一次正式向外公布時間)。Linux存在着許多不同的Linux版本,但它們都使用了Linux核心。

Linux具備驚人的可移植性。可安裝在各種計算機硬體裝置中,比如手機、平闆電腦、路由器、視訊遊戲控制台、台式計算機、大型機和超級計算機。

嚴格來講,Linux這個詞本身隻表示Linux核心,但實際上人們已經習慣了用Linux來形容整個基于Linux核心,并且使用GNU 工程各種工具和資料庫的作業系統。

在這幾個簡要的段落中。有不少新的名詞被引入了進來。下面我對幾個重要的概念進行描述。

A、關于類UNIX系統

類Unix系統(英文:Unix-like)指各種傳統的Unix系統(比如FreeBSD、OpenBSD、SUN公司的Solaris)以及各種與傳統Unix類似的系統(例如Minix、Linux、QNX等)。

它們雖然有的是自由軟體,有的是商業軟體,但都相當程度地繼承了原始UNIX的特性,有許多相似處,并且都在一定程度上遵守POSIX規範。

這個在一些經典的作業系統教科書中已經作了說明。我們僅需知道。它和我們熟知的Windows系列作業系統一樣。都是一種現代作業系統。對底層的計算機資源進行抽象。對上層使用者提供調用接口。完成計算機應該完成的功能。

B、關于可移植性

可移植性指與軟體從某一環境轉移到另一環境下的難易程度。為獲得較高的可移植性,在設計過程中常采用通用的程式設計語言和運作支撐環境。盡量不用與系統的底層相關性強的語言。

可移植性是軟體品質之一,良好的可移植性可以提高軟體的生命周期。代碼的可移植性主題是軟體;可移植性是軟體産品的一種能力屬性,其行為表現為一種程度,而表現出來的程度與環境密切相關。

一個作業系統的可移植性往往表現在它能在運作在不同的體系結構上。感性的了解就是可以支援的裝置有很多。比如前文所說的,Linux可以運作在大型伺服器上。各種平闆電腦上。

前段時間有黑客成功的把Linux移植到一個佳能照相機上。并且在這個照相機上運作了一些主流的軟體。可以說。隻要有足夠可以利用的硬體資源。就可以把Linux移植到這個硬體平台上去。這個資源的最低要求往往很低。這可以與對硬體資源要求很高的Windows有一個鮮明的對比。舉個例子就是。當Windows 10的更新提示從你計算機的右下角彈出時。

你可以不假思索的點選‘馬上更新’嗎?我想大多數人對這個問題的答案是否定的。為什麼?因為大多數情況下。更新之後就會變得更卡。延遲更大。一些無用而龐大的軟體瘋狂的占用你有限的計算機資源。而如果你選擇的是Linux。你幾乎可以任意的在計算機上安裝軟體。運作程式(如果你的記憶體不是太小。且硬碟交換分區足夠的話)。

Linux核心已經将有限的硬體資源發揮到了極緻。開源軟體良好的子產品化設計在各個層次上充分利用了程式的局部性原理。(當然這是在損失了一定易用性的前提下的。)。不好意思我扯遠了。這些不是本文的重點。。

由于筆者沒有土豪到有很多計算機。是以選擇了一款比較便宜的ARM9開發闆作為開發平台。它的CPU是三星公司生産的S3C2440。核心是ARM920T。

C、關于Linux的基本思想

Linux的基本思想有兩點:

第一. 一切都是檔案。系統中的所有都歸結為一個檔案,包括指令、硬體和軟體裝置、作業系統、程序等等對于作業系統核心而言,都被視為擁有各自特性或類型的檔案。至于說Linux是基于Unix的,很大程度上也是因為這兩者的基本思想十分相近

第二. 每個軟體都有确定的用途。

D、關于Linux的特點

完全免費

Linux是一款免費的作業系統,使用者可以通過網絡或其他途徑免費獲得,并可以任意修改其源代碼。這是其他的作業系統所做不到的。

正是由于這一點,來自全世界的無數程式員參與了Linux的修改、編寫工作,程式員可以根據自己的興趣和靈感對其進行改變,這讓Linux吸收了無數程式員的精華,不斷壯大。

完全相容POSIX1.0标準

這使得可以在Linux下通過相應的模拟器運作常見的DOS、Windows的程式。這為使用者從Windows轉到Linux奠定了基礎。

許多使用者在考慮使用Linux時,就想到以前在Windows下常見的程式是否能正常運作,這一點就消除了他們的疑慮。

多使用者、多任務

Linux支援多使用者,各個使用者對于自己的檔案裝置有自己特殊的權利,保證了各使用者之間互不影響。多任務則是現在電腦最主要的一個特點,Linux可以使多個程式同時并獨立地運作。

良好的界面

Linux同時具有字元界面和圖形界面。在字元界面使用者可以通過鍵盤輸入相應的指令來進行操作。它同時也提供了類似Windows圖形界面的X-Window系統,使用者可以使用滑鼠對其進行操作。在X-Window環境中就和在Windows中相似,可以說是一個Linux版的Windows。

支援多種平台

Linux可以運作在多種硬體平台上,如具有x86、680x0、SPARC、Alpha等處理器的平台。此外Linux還是一種嵌入式作業系統,可以運作在掌上電腦、機頂盒或遊戲機上。2001年1月份釋出的Linux 2.4版核心已經能夠完全支援Intel 64位晶片架構。同時Linux也支援多處理器技術。多個處理器同時工作,使系統性能大大提高。

檔案類型

普通檔案(regular file):就是一般存取的檔案,由ls-al顯示出來的屬性中,第一個屬性為 [-],例如 [-rwxrwxrwx]。另外,依照檔案的内容,又大緻可以分為:

1、純文字檔案(ASCII):這是Unix系統中最多的一種檔案類型,之是以稱為純文字檔案,是因為内容可以直接讀到的資料,例如數字、字母等等。設定檔案幾乎都屬于這種檔案類型。舉例來說,使用指令“cat ~/.bashrc”就可以看到該檔案的内容(cat是将檔案内容讀出來)。

2、二進制檔案(binary):系統其實僅認識且可以執行二進制檔案(binary file)。Linux中的可執行檔案(腳本,文本方式的批處理檔案不算)就是這種格式的。舉例來說,指令cat就是一個二進制檔案。

3、資料格式的檔案(data):有些程式在運作過程中,會讀取某些特定格式的檔案,那些特定格式的檔案可以稱為資料檔案(data file)。舉例來說,Linux在使用者登入時,都會将登入資料記錄在 /var/log/wtmp檔案内,該檔案是一個資料檔案,它能通過last指令讀出來。但使用cat時,會讀出亂碼。因為它是屬于一種特殊格式的檔案。

4、目錄檔案(directory):就是目錄,第一個屬性為[d],例如 [drwxrwxrwx]。

連接配接檔案(link):類似Windows下面的快捷方式。第一個屬性為 [l],例如 [lrwxrwxrwx]。

5、裝置與裝置檔案(device):與系統外設及存儲等相關的一些檔案,通常都集中在 /dev目錄。通常又分為兩種:

塊裝置檔案:就是存儲資料以供系統存取的接口裝置,簡單而言就是硬碟。例如一号硬碟的代碼是 /dev/hda1等檔案。第一個屬性為 [b]。

字元裝置檔案:即串行端口的接口裝置,例如鍵盤、滑鼠等等。第一個屬性為 [c]。

6、套接字(sockets):這類檔案通常用在網絡資料連接配接。可以啟動一個程式來監聽用戶端的要求,用戶端就可以通過套接字來進行資料通信。第一個屬性為 [s],最常在 /var/run目錄中看到這種檔案類型。

7、管道(FIFO,pipe):FIFO也是一種特殊的檔案類型,它主要的目的是,解決多個程式同時存取一個檔案所造成的錯誤。FIFO是first-in-first-out(先進先出)的縮寫。第一個屬性為 [p]

檔案結構

/:根目錄,所有的目錄、檔案、裝置都在/之下,/就是Linux檔案系統的組織者,也是最上級的上司者。

/bin:bin 就是二進制(binary)英文縮寫。在一般的系統當中,都可以在這個目錄下找到linux常用的指令。系統所需要的那些指令位于此目錄。

/boot:Linux的核心及引導系統程式所需要的檔案目錄,比如 vmlinuz initrd.img 檔案都位于這個目錄中。在一般情況下,GRUB或LILO系統引導管理器也位于這個目錄。

/cdrom:這個目錄在剛剛安裝系統的時候是空的。可以将光驅檔案系統挂在這個目錄下。例如:mount /dev/cdrom /cdrom

/dev:dev 是裝置(device)的英文縮寫。這個目錄對所有的使用者都十分重要。因為在這個目錄中包含了所有linux系統中使用的外部裝置。但是這裡并不是放的外部裝置的驅動程式。這一點和常用的windows,dos作業系統不一樣。它實際上是一個通路這些外部裝置的端口。可以非常友善地去通路這些外部裝置,和通路一個檔案,一個目錄沒有任何差別。

/etc:etc這個目錄是linux系統中最重要的目錄之一。在這個目錄下存放了系統管理時要用到的各種配置檔案和子目錄。要用到的網絡配置檔案,檔案系統,x系統配置檔案,裝置配置資訊,設定使用者資訊等都在這個目錄下。

/home:如果建立一個使用者,使用者名是"xx",那麼在/home目錄下就有一個對應的/home/xx路徑,用來存放使用者的主目錄。

/lib:lib是庫(library)英文縮寫。這個目錄是用來存放系統動态連接配接共享庫的。幾乎所有的應用程式都會用到這個目錄下的共享庫。是以,千萬不要輕易對這個目錄進行什麼操作,一旦發生問題,系統就不能工作了。

/lost+found:在ext2或ext3檔案系統中,當系統意外崩潰或機器意外關機,而産生一些檔案碎片放在這裡。當系統啟動的過程中fsck工具會檢查這裡,并修複已經損壞的檔案系統。有時系統發生問題,有很多的檔案被移到這個目錄中,可能會用手工的方式來修複,或移到檔案到原來的位置上。

/mnt:這個目錄一般是用于存放挂載儲存裝置的挂載目錄的,比如有cdrom等目錄。可以參看/etc/fstab的定義。

/media:有些linux的發行版使用這個目錄來挂載那些usb接口的移動硬碟(包括U盤)、CD/DVD驅動器等等。

/opt:這裡主要存放那些可選的程式。

/proc:可以在這個目錄下擷取系統資訊。這些資訊是在記憶體中,由系統自己産生的。

/root:Linux超級權限使用者root的家目錄。

/sbin:這個目錄是用來存放系統管理者的系統管理程式。大多是涉及系統管理的指令的存放,是超級權限使用者root的可執行指令存放地,普通使用者無權限執行這個目錄下的指令,這個目錄和/usr/sbin; /usr/X11R6/sbin或/usr/local/sbin目錄是相似的,凡是目錄sbin中包含的都是root權限才能執行的。

/selinux :對SElinux的一些配置檔案目錄,SElinux可以讓linux更加安全。

/srv 服務啟動後,所需通路的資料目錄,舉個例子來說,www服務啟動讀取的網頁資料就可以放在/srv/www中

/tmp:臨時檔案目錄,用來存放不同程式執行時産生的臨時檔案。有時使用者運作程式的時候,會産生臨時檔案。/tmp就用來存放臨時檔案的。/var/tmp目錄和這個目錄相似。

/usr:這是linux系統中占用硬碟空間最大的目錄。使用者的很多應用程式和檔案都存放在這個目錄下。在這個目錄下,可以找到那些不适合放在/bin或/etc目錄下的額外的工具

/usr/local:這裡主要存放那些手動安裝的軟體,即不是通過“新立得”或apt-get安裝的軟體。它和/usr目錄具有相類似的目錄結構。讓軟體包管理器來管理/usr目錄,而把自定義的腳本(scripts)放到/usr/local目錄下面、。

/usr/share :系統共用的東西存放地,比如/usr/share/fonts 是字型目錄,/usr/share/doc和/usr/share/man幫助檔案。

/var:這個目錄的内容是經常變動的,看名字就知道,可以了解為vary的縮寫,/var下有/var/log 這是用來存放系統日志的目錄。/var/ www目錄是定義Apache伺服器站點存放目錄;/var/lib 用來存放一些庫檔案,比如MySQL的,以及MySQL資料庫的的存放地。

如上。相信讀者已經對Linux作業系統有了一個概觀。對于一些具體指令。筆者決定需要用到的時候再做說明。現在我們來看看第二個概念:

2、什麼是Linux裝置驅動

裝置驅動最通俗的解釋就是驅使硬體裝置行動。驅動與底層硬體直接打交道,按照硬體裝置的具體工作方式,讀寫裝置的寄存器,完成裝置的輪詢、中斷處理、DMA通信,進行實體記憶體向虛拟記憶體的映射等,最終讓通信裝置能收發資料,讓顯示裝置能顯示文字和畫面,讓儲存設備能記錄檔案和資料。

Linux裝置驅動是對底層硬體資源的抽象。對上層的作業系統其他服務提供一個良好的接口。讓其他服務可以把一個特定的硬體。或是一種機制當做一個檔案使用。使用通用的系統調用進行調用。

3、關于序列槽的種種

衆所周知。我們現在的計算機上面有很多接口。如USB。網口。并口等。序列槽總線是其中的一個。串行接口簡稱序列槽,也稱串行通信接口或串行通訊接口(通常指COM接口),是采用串行通信方式的擴充接口。

串行接口 (Serial Interface) 是指資料一位一位地順序傳送,其特點是通信線路簡單,隻要一對傳輸線就可以實作雙向通信(可以直接利用電話線作為傳輸線),進而大大降低了成本,特别适用于遠距離通信,但傳送速度較慢。一條資訊的各位資料被逐位按順序傳送的通訊方式稱為串行通訊。

串行通訊的特點是:資料位的傳送,按位順序進行,最少隻需一根傳輸線即可完成;成本低但傳送速度慢。串行通訊的距離可以從幾米到幾千米;根據資訊的傳送方向,串行通訊可以進一步分為單工、半雙工和全雙工三種。

序列槽通信的兩種最基本的方式:同步串行通信方式和異步串行通信方式。

同步串行是指SPI(SerialPeripheral interface)的縮寫,顧名思義就是串行外圍裝置接口。SPI總線系統是一種同步串行外設接口,它可以使MCU與各種外圍裝置以串行方式進行通信以交換資訊,TRM450是SPI接口。

異步串行是指UART(UniversalAsynchronous Receiver/Transmitter),通用異步接收/發送。UART是一個并行輸入成為串行輸出的晶片,通常內建在主機闆上。UART包含TTL電平的序列槽和RS232電平的序列槽。

TTL電平是3.3V的,而RS232是負邏輯電平,它定義+5+12V為低電平,而-12-5V為高電平,MDS2710、MDS SD4、EL805等是RS232接口,EL806有TTL接口。

串行接口按電氣标準及協定來分包括RS-232-C、RS-422、RS485等。

RS-232

也稱标準序列槽,最常用的一種串行通訊接口。它是在1970年由美國電子工業協會(EIA)聯合貝爾系統、數據機廠家及計算機終端生産廠家共同制定的用于串行通訊的标準。

它的全名是“資料終端裝置(DTE)和資料通訊裝置(DCE)之間串行二進制資料交換接口技術标準”。傳統的RS-232-C接口标準有22根線,采用标準25芯D型插頭座(DB25),後來使用簡化為9芯D型插座(DB9),現在應用中25芯插頭座已很少采用。

RS-232采取不平衡傳輸方式,即所謂單端通訊。由于其發送電平與接收電平的差僅為2V至3V左右,是以其共模抑制能力差,再加上雙絞線上的分布電容,其傳送距離最大為約15米,最高速率為20kb/s。RS-232是為點對點(即隻用一對收、發裝置)通訊而設計的,其驅動器負載為3~7kΩ。是以RS-232适合本地裝置之間的通信。

RS-422

标準全稱是“平衡電壓數字接口電路的電氣特性”,它定義了接口電路的特性。典型的RS-422是四線接口。實際上還有一根信号地線,共5根線。其DB9連接配接器引腳定義。由于接收器采用高輸入阻抗和發送驅動器比RS232更強的驅動能力,故允許在相同傳輸線上連接配接多個接收節點,最多可接10個節點。

即一個主裝置(Master),其餘為從裝置(Slave),從裝置之間不能通信,是以RS-422支援點對多的雙向通信。接收器輸入阻抗為4k,故發端最大負載能力是10×4k+100Ω(終接電阻)。

RS-422四線接口由于采用單獨的發送和接收通道,是以不必控制資料方向,各裝置之間任何必須的信号交換均可以按軟體方式(XON/XOFF握手)或硬體方式(一對單獨的雙絞線)實作。

RS-422的最大傳輸距離為1219米,最大傳輸速率為10Mb/s。其平衡雙絞線的長度與傳輸速率成反比,在100kb/s速率以下,才可能達到最大傳輸距離。隻有在很短的距離下才能獲得最高速率傳輸。一般100米長的雙絞線上所能獲得的最大傳輸速率僅為1Mb/s。

RS-485

是從RS-422基礎上發展而來的,是以RS-485許多電氣規定與RS-422相仿。如都采用平衡傳輸方式、都需要在傳輸線上接終接電阻等。RS-485可以采用二線與四線方式,二線制可實作真正的多點雙向通信,而采用四線連接配接時,與RS-422一樣隻能實作點對多的通信,即隻能有一個主(Master)裝置,其餘為從裝置,但它比RS-422有改進,無論四線還是二線連接配接方式總線上可多接到32個裝置。

RS-485與RS-422的不同還在于其共模輸出電壓是不同的,RS-485是-7V至+12V之間,而RS-422在-7V至+7V之間,RS-485接收器最小輸入阻抗為12kΩ、RS-422是4kΩ;由于RS-485滿足所有RS-422的規範,是以RS-485的驅動器可以在RS-422網絡中應用。

RS-485與RS-422一樣,其最大傳輸距離約為1219米,最大傳輸速率為10Mb/s。平衡雙絞線的長度與傳輸速率成反比,在100kb/s速率以下,才可能使用規定最長的電纜長度。隻有在很短的距離下才能獲得最高速率傳輸。一般100米長雙絞線最大傳輸速率僅為1Mb/s。

筆者采用的RS-232序列槽通信協定。下面對其通信接線方法做簡要說明。目前較為常用的序列槽有9針序列槽(DB9)和25針序列槽(DB25),通信距離較近時(<12m),可以用電纜線直接連接配接标準RS232端口(RS422,RS485較遠),若距離較遠,需附加數據機(MODEM)或其他相關裝置。最為簡單且常用的是三線制接法,即地、接收資料和發送資料三腳相連,這是最為基本的接法,且直接用RS232相連。

上面是對微機标準串行口而言的,還有許多非标準裝置,不做說明。

好了。到此為止我們已經解決了一開始的三個問題。讓我們進入實際的代碼。實際的硬體來進行分析。

在一個硬體平台上。硬體是可用的。我們必須要燒寫适當的軟體到平台的RAM中。這樣CPU才能跳轉到最先的指令。然後慢慢加載各種資源。才能完成系統的自舉。

一般我們采用BootLoader進行硬體的初始化。并引導至作業系統核心。

筆者采用的BootLoader是u-Boot-1.1.16。Uboot是一個衆所周知的開源軟體。讀者僅需了解它起到了BootLoader的作用即可。這裡不多做解釋。僅對序列槽的連接配接和程式的下載下傳作簡要說明:

嵌入式驅動解析:從序列槽驅動到Linux驅動模型

将UBOOT目錄下的u-boot.bin下載下傳到開發平台上。在Windows打開裝置管理器。選擇端口。進而找到正确的com口号。在此之前確定開發闆的序列槽與筆記本的USB口連接配接。(因為現在筆記本都沒有并口了。是以隻能采用USB轉序列槽線。搭配開發闆上的電平轉換晶片來完成序列槽連接配接目的。)

嵌入式驅動解析:從序列槽驅動到Linux驅動模型

然後我們再使用一個工具。即SecureCRT。找到對應的com号。完成快速連結。波特率選擇115200。取消流控。

嵌入式驅動解析:從序列槽驅動到Linux驅動模型

如果一切順利。在筆記本上就可以看到序列槽的類似下面的輸出。這就是傳說中的序列槽控制台。

嵌入式驅動解析:從序列槽驅動到Linux驅動模型

這個序列槽的指令功能是由Uboot本身完成的。并不是linux下的序列槽驅動。

引入此圖旨在讓讀者感性的認識到序列槽控制台的功能是什麼。

下面正式開始對序列槽打開。發送。接收函數的分析。這裡向前引用一個函數。就是linux核心中幾種2440晶片通用的序列槽發送函數s3c24xx_serial_start_tx。函數聲明為static voids3c24xx_serial_start_tx(struct uart_port *port):函數定義在./linux/driver/tty/serial/samsung.c中。

好了。我們從這個目錄結構開始。說明大概的tty子系統驅動模型。

首先。最前面的linux是核心代碼的根目錄。如圖所示:

嵌入式驅動解析:從序列槽驅動到Linux驅動模型

至此。我們面臨一個問題。linux核心是什麼。

Linux核心是什麼?

Linux是一種開源電腦作業系統核心。它是一個用C語言寫成,符合POSIX标準的類Unix作業系統。

Linux最早是由芬蘭黑客Linus Torvalds為嘗試在英特爾x86架構上提供自由免費的類Unix作業系統而開發的。該計劃開始于1991年,在計劃的早期有一些Minix 黑客提供了協助,而今天全球無數程式員正在為該計劃無償提供幫助。

Linux是一個一體化核心(monolithickernel)系統。“核心”指的是一個提供硬體抽象層、磁盤及檔案系統控制、多任務等功能的系統軟體。一個核心不是一套完整的作業系統。

一套基于Linux核心的完整作業系統叫作Linux作業系統,或是GNU/Linux。裝置驅動程式可以完全通路硬體。Linux内的裝置驅動程式可以友善地以子產品化(modularize)的形式設定,并在系統運作期間可直接裝載或解除安裝。

作業系統是一個用來和硬體打交道并為使用者程式提供一個有限服務集的低級支撐軟體。一個計算機系統是一個硬體和軟體的共生體,它們互相依賴,不可分割。計算機的硬體,含有外圍裝置、處理器、記憶體、硬碟和其他的電子裝置組成計算機的發動機。但是沒有軟體來操作和控制它,自身是不能工作的。

完成這個控制工作的軟體就稱為作業系統,在Linux的術語中被稱為“核心”,也可以稱為“核心”。Linux核心的主要子產品(或元件)分以下幾個部分:存儲管理、CPU和程序管理、檔案系統、裝置管理和驅動、網絡通信,以及系統的初始化(引導)、系統調用等。

系統調用接口

SCI 層提供了某些機制執行從使用者空間到核心的函數調用。正如前面讨論的一樣,這個接口依賴于體系結構,甚至在相同的處理器家族内也是如此。SCI 實際上是一個非常有用的函數調用多路複用和多路分解服務。在 ./linux/kernel 中您可以找到 SCI 的實作,并在 ./linux/arch 中找到依賴于體系結構的部分。

程序管理

程序管理的重點是程序的執行。在核心中,這些程序稱為線程,代表了單獨的處理器虛拟化(線程代碼、資料、堆棧和 CPU寄存器)。在使用者空間,通常使用程序這個術語,不過 Linux 實作并沒有區分這兩個概念(程序和線程)。

核心通過 SCI 提供了一個應用程式程式設計接口(API)來建立一個新程序(fork、exec 或 Portable Operating System Interface [POSⅨ] 函數),停止程序(kill、exit),并在它們之間進行通信和同步(signal 或者 POSⅨ機制)。

程序管理還包括處理活動程序之間共享 CPU的需求。核心實作了一種新型的排程算法,不管有多少個線程在競争 CPU,這種算法都可以在固定時間内進行操作。這種算法就稱為 O⑴排程程式,這個名字就表示它排程多個線程所使用的時間和排程一個線程所使用的時間是相同的。

O⑴排程程式也可以支援多處理器(稱為對稱多處理器或 SMP)。您可以在 ./linux/kernel 中找到程序管理的源代碼,在 ./linux/arch 中可以找到依賴于體系結構的源代碼。

記憶體管理

核心所管理的另外一個重要資源是記憶體。為了提高效率,如果由硬管理虛拟記憶體,記憶體是按照所謂的記憶體頁方式進行管理的(對于大部分體系結構來說都是 4KB)。Linux 包括了管理可用記憶體的方式,以及實體和虛拟映射所使用的硬體機制。

不過記憶體管理要管理的可不止 4KB緩沖區。Linux 提供了對 4KB緩沖區的抽象,例如 slab 配置設定器。這種記憶體管理模式使用 4KB緩沖區為基數,然後從中配置設定結構,并跟蹤記憶體頁使用情況,比如哪些記憶體頁是滿的,哪些頁面沒有完全使用,哪些頁面為空。這樣就允許該模式根據系統需要來動态調整記憶體使用。

為了支援多個使用者使用記憶體,有時會出現可用記憶體被消耗光的情況。由于這個原因,頁面可以移出記憶體并放入磁盤中。這個過程稱為交換,因為頁面會被從記憶體交換到硬碟上。記憶體管理的源代碼可以在 ./linux/mm 中找到。

虛拟檔案系統

虛拟檔案系統(VFS)是 Linux 核心中非常有用的一個方面,因為它為檔案系統提供了一個通用的接口抽象。VFS 在 SCI 和核心所支援的檔案系統之間提供了一個交換層。

VFS 在使用者和檔案系統之間提供了一個交換層

在 VFS 上面,是對諸如 open、close、read 和 write 之類的函數的一個通用 API 抽象。在 VFS 下面是檔案系統抽象,它定義了上層函數的實作方式。它們是給定檔案系統(超過 50 個)的插件。檔案系統的源代碼可以在 ./linux/fs 中找到。

檔案系統層之下是緩沖區緩存,它為檔案系統層提供了一個通用函數集(與具體檔案系統無關)。這個緩存層通過将資料保留一段時間(或者随即預先讀取資料以便在需要是就可用)優化了對實體裝置的通路。緩沖區緩存之下是裝置驅動程式,它實作了特定實體裝置的接口。

好了。相信讀者已經對linuxkernel 有了一個概觀。下面我們繼續分析這個路徑背後代表的模型結構。(./linux/driver/tty/serial/samsung.c)

driver是驅動程式的目錄。如圖所示:

嵌入式驅動解析:從序列槽驅動到Linux驅動模型

前文對linux裝置驅動程式有了一個大概的描述。下面我們具體看一下linux下的驅動。

縱覽linux/drivers目錄,大概還有35個以上的子目錄,每個子目錄基本上就代表了一種裝置驅動,有atm、block、char、misc、input、net、usb、sound、video等。這裡隻描述在嵌入式系統裡面用得最為廣泛的3種裝置。

1.字元裝置(char device)

字元裝置是Linux最簡單的裝置,可以像檔案一樣通路。初始化字元裝置時,它的裝置驅動程式向Linux登記,并在字元裝置向量表中增加一個device_struct資料結構條目,這個裝置的主裝置辨別符用做這個向量表的索引。

一個裝置的主裝置辨別符是固定的。chrdevs向量表中的每一個條目,一個device_struct資料結構,包括兩個元素:一個登記裝置驅動程式名稱的指針和一個指向一組檔案操作的指針。可以參考的代碼是include/linux/ major.h。

一般來說像滑鼠、序列槽、鍵盤等裝置都屬于字元裝置。

2.塊裝置(block device)

塊裝置是檔案系統的物質基礎,它也可以像檔案一樣被通路。Linux用blkdevs向量表維護已經登記的塊裝置檔案。它像chrdevs向量表一樣,使用裝置的主裝置号作為索引。它的條目也是device_struct資料結構。與字元裝置不同的是,塊裝置分為SCSI類和IDE類。

向Linux核心登記并向核心提供檔案操作。一種塊裝置類的裝置驅動程式向這種類提供和類相關的接口。可以參考的代碼是fs/devices.c。

每一個塊裝置驅動程式必須提供普通的檔案操作接口和對于buffer cache的接口。每一個塊裝置驅動程式填充blk_dev向量表中的blk_dev_struct資料結構。此向量表的索引是裝置的主裝置号。其中blk_dev_struct資料結構包括一個請求例程的位址和一個指針,指向一個request資料結構的清單,每一個都表達buffer cache向裝置讀/寫一塊資料的一個請求。

可以參考的源代碼是drivers/block/ll_rw_blk.c和include/linux/blkdev.h。

當buffer cache從一個已登記的裝置讀/寫一塊資料,或者希望讀、寫一塊資料到其他位置時,就在blk_dev_struct中增加一個request資料結構。每個request資料結構都有一個指向一個或多個buffer_head資料結構的指針,每一個都是讀/寫一塊資料的請求。

如果buffer_head資料結構被鎖定(buffer_cache),可能會有一個程序在等待這個緩沖區的阻塞程序完成。每一個request資料結構都是從all_request表中配置設定的。如果request增加到空的request清單中,就調用驅動程式的request函數處理這個request隊列,否則驅動程式隻是簡單地處理request隊列中的每一個請求。

塊裝置驅動程式和字元裝置驅動程式的主要差別是:在對字元裝置發出讀、寫請求時,實際的硬體I/O一般緊接着就發生了,塊裝置則不然,它利用一塊系統記憶體作為緩沖區,當使用者程序對裝置請求能滿足使用者的要求時,就傳回請求的資料,如果不能就調用請求函數來進行實際的I/O操作。塊裝置是主要針對磁盤等慢速裝置的,以免耗費過多的CPU時間來等待。

塊裝置主要有硬碟、CD光牒驅動器等。可以檢視檔案/proc/devices獲得。

3.網絡裝置(net device)

網絡裝置在系統中的作用類似于一個已挂載的塊裝置。塊裝置将自己注冊到blk_dev資料及其他核心結構中,然後通過自己的request函數在發生請求時傳輸和接收資料塊,同樣網絡裝置也必須在特定的資料結構中注冊自己,以便與外界交換資料包時被調用。網絡裝置在Linux裡做專門的處理。Linux的網絡系統主要是基于BSD UNIX的Socket機制。在系統和驅動程式之間定義有專門的資料結構(sk_buff)進行資料的傳遞。系統裡支援對發送資料和接收資料的緩存,提供流量控制機制,提供對多協定的支援。

4.雜項裝置(misc device)

雜項裝置也是在嵌入式系統中用得比較多的一種裝置驅動,在第11章裡面介紹的sub LCD和弦晶片的驅動等都是采用 misc device 的驅動方式實作的。在 Linux 核心的include\linux目錄下有Miscdevice.h檔案,要把自己定義的misc device從裝置定義在這裡。其實是因為這些字元裝置不符合預先确定的字元裝置範疇,所有這些裝置采用主編号10,一起歸于misc device,其實misc_register就是用主标号10調用register_chrdev()的。

這是driver目錄下的分類。我們主要調研的序列槽驅動。屬于TTY子系統。是以我們cd到tty目錄下。ls顯示裡面的檔案。如圖所示:

嵌入式驅動解析:從序列槽驅動到Linux驅動模型

下面對linux核心tty裝置做一點簡要說明。

tty一詞源于Teletypes,或Teletypewriters,它是最早出現的一種終端裝置,類似電傳打字機,由Teletype公司生産。最初tty是指連接配接到Unix系統上的實體或者虛拟終端。終端是一種字元型裝置,通常使用tty來統稱各種類型的終端裝置。随着時間的推移,當通過串行口能夠建立起終端連接配接後,這個名字也用來指任何的序列槽裝置。

它還有多種類,例如序列槽(ttySn、ttySACn、ttyOn)、USB到序列槽的轉換器(ttyUSBn),還有需要特殊處理才能正常工作的數據機(比如傳統的WinModem類裝置)等。tty虛拟裝置支援虛拟控制台,它能通過鍵盤及網絡連接配接或者通過xterm會話登入到計算機上。

其實起初終端和控制台都不是個人電腦的概念,而是多人共用的小型中型大型計算機上的概念。終端為主機提供了人機接口,每個人都通過終端使用主機的資源。終端有字元終端和圖形終端兩種。一台主機可以連很多終端。控制台是一種特殊的人機接口, 是人控制主機的第一人機接口。

而主機對于控制台的信任度高于其他終端。對此還可以結合核心啟動代碼中init程序打開/dev/console和執行兩次sys_dup(0),以及标準輸入、标準輸出、标準出錯,還有就是程序fork後的标準輸入輸出的複制情況來一起了解。而個人計算機隻有控制台,沒有終端。當然願意的話,可以在序列槽上連一兩台字元啞終端。

但是linux按POSIX标準把個人計算機當成小型機來用,在控制台上通過getty軟體虛拟了六個字元啞終端(或者叫虛拟控制台終端tty1-tty6)(數量可以在/etc/inittab裡自己調整)和一個圖型終端, 在虛拟圖形終端中又可以通過軟體(如rxvt)再虛拟無限多個僞終端(pts/0等)。

但這全是虛拟的,雖然用起來一樣,但實際上沒有實體實體。是以在個人計算機上,隻有一個實際的控制台,沒有終端,所有終端都是在控制台上用軟體模拟的。要把個人計算機當主機再通過序列槽或網卡外連真正的實體終端也可以,論成本,呵呵。誰會怎麼做呢。

終端按照其自身能力分類,可以分為:

1、啞終端(瘦用戶端)

早期的計算機終端是通過串行RS-232通信的,它隻能解釋有限數量的控制碼(CR,LF等),但沒有能力處理執行特殊的轉義序列功能(如清行、清屏或控制光标的位置)。簡單來說就是處理能力有限的終端機,他們一般基本上隻具有和機械電傳打字機類似的有限功能。這種類型的終端稱為啞終端。

現在仍然在現代類Unix系統上得到支援,通過設定環境變量TERM=dumb。啞終端有時用來指任何類型的通過RS-232連接配接的傳統計算機終端,不對資料進行本地處理或本地執行使用者程式的串行通信終端。啞終端有時也指功能有限,隻有單色文本處理能力或直接傳輸每一個鍵入的字元而不等待主機輪詢的公共計算機終端。

2、智能終端(胖用戶端)

智能終端就是有能力處理轉義序列,也就是說處理能力較強的終端機。

Linux系統的終端裝置一般有以下幾種:

1、 控制台

系統控制台/dev/console

/dev/console是系統控制台,是與作業系統互動的裝置。系統所産生的資訊會發送到該裝置上。平時我們看到的PC隻有一個螢幕和鍵盤,它其實就是控制台。目前隻有在單使用者模式下,才允許使用者登入控制台/dev/console。(可以在單使用者模式下輸入tty指令進行确認)。

console有緩沖的概念,為核心提供列印輸出。核心把要列印的内容裝入緩沖區__log_buff,然後由console來決定列印到哪裡(比如是tty0還是ttySn等)。console指向激活的終端。曆史上,console指主機本身的螢幕和鍵盤,而tty指用電纜連結的其它位置的控制台。

某些情況下console和tty0是一緻的,就是目前所使用的是虛拟終端,也是激活虛拟終端。是以有些資料中稱/dev/console是到/dev/tty0的符号連結,但是這樣說現在看來是不對的:根據核心文檔,在2.1.71之前,/dev/console根據不同系統設定,符号連結到/dev/tty0或者其他tty*上,在2.1.71版本之後則完全由核心代碼内部控制它的映射。

如果一個終端裝置要實作console功能,必須向核心注冊一個struct console結構,一般的序列槽驅動中都會有。如果裝置要實作tty功能,必須要核心的tty子系統注冊一個struct tty_driver結構,注冊函數在drivers/tty/tty_io.c中。一個裝置可以同時實作console和tty_driver,一般序列槽都這麼做。

目前控制台:/dev/tty

這是應用程式中的概念,如果目前程序有控制終端(Controlling Terminal),那麼/dev/tty就是目前程序控制台的裝置檔案。對于你登入的shell,/dev/tty就是你使用的控制台,裝置号是(5,0)。不過它并不指任何實體意義上的控制台,/dev/tty會映射到目前裝置(使用指令“tty”可以檢視它具體對應哪個實際實體控制台裝置)。輸出到/dev/tty的内容隻會顯示在目前工作終端上(無論是登入在ttyn中還是pty中)。

你如果在控制台界面下(即字元界面下)那麼dev/tty就是映射到dev/tty1-6之間的一個(取決于你目前的控制台号),但是如果你現在是在圖形界面(Xwindows),那麼你會發現現在的/dev/tty映射到的是/dev/pts的僞終端上。/dev/tty有些類似于到實際所使用終端裝置的一個聯接。

你可以輸入指令“tty",将顯示目前映射終端如:/dev/tty1或者/dev/pts/0等。也可以使用指令“ps -ax”來檢視其他程序與哪個控制終端相連。

在目前終端中輸入 echo “tekkaman” > /dev/tty ,都會直接顯示在目前的終端中。

虛拟控制台 /dev/ttyn

/dev/ttyn是程序虛拟控制台,他們共享同一個真實的實體控制台。如果在程序裡打開一個這樣的檔案且該檔案不是其他程序的控制台時,那該檔案就是這個程序的控制台。

程序printf資料會輸出到這裡。在PC上,使用者可以使用alt+Fn切換控制台,現在不知道怎麼回事我用Ctrl + Alt + Fn才能切換。這沒具體看過為啥。可能是Linux沒有繼承UNIX這方面的傳統罷了。看起來感覺存在多個螢幕,這種虛拟控制台對應tty1~n,其中:

/dev/tty1等代表第一個虛拟控制台

例如當使用ALT+F2進行切換時,系統的虛拟控制台為/dev/tty2 ,目前控制台(/dev/tty)則指向/dev/tty2

在UNIX系統中,計算機顯示器通常被稱為控制台(Console)。它仿真了類型為Linux的一種終端,并且有一些裝置特殊檔案與之相關聯:tty0、tty1、tty2等。當你在控制台上登入時,使用的是tty1。使用Alt+[F1—F6]組合鍵時,我們就可以切換到tty2、tty3等上面去。

讀者可以登入到不同的虛拟控制台上去,因而可以讓系統同時有幾個不同的會話存在。

而比較特殊的是/dev/tty0,他代表目前虛拟控制台,其實就是目前所使用虛拟控制台的一個别名。是以不管目前正在使用哪個虛拟控制台(注意:這裡是虛拟控制台,不包括僞終端),系統資訊都會重定位到/dev/tty0上。

隻有系統或超級使用者root可以向/dev/tty0進行寫操作。tty0是系統自動打開的,但不用于使用者登入。在Framebuffer裝置沒有啟用的系統中,可以使用/dev/tty0通路顯示卡。

2、 僞終端pty(pseudo-tty)

僞終端(Pseudo Terminal)是終端的發展,為滿足現在需求(比如網絡登陸、xwindow視窗的管理)。它是成對出現的邏輯終端裝置(即master和slave裝置, 對master的操作會反映到slave上)。它多用于模拟終端程式,是遠端登陸(telnet、ssh、xterm等)後建立的控制台裝置。

曆史上,有兩套僞終端軟體接口:

BSD接口:較簡單,master為/dev/pty [p-za-e] [0-9a-f];slave為 /dev/tty [p-za-e] [0-9a-f] ,它們都是配對的出現的。例如/dev/ptyp3和/dev/ttyp3。但由于在程式設計時要找到一個合适的終端需要逐個嘗試,是以逐漸被放棄。

Unix 98接口:使用一個/dev/ptmx作為master裝置,在每次打開操作時會得到一個master裝置fd,并在/dev/pts/目錄下得到一個slave裝置(如 /dev/pts/3和/dev/ptmx),這樣就避免了逐個嘗試的麻煩。

由于可能有好幾千個使用者登陸,是以/dev/pts/* 是動态生成的,不象其他裝置檔案是建構系統時就已經産生的硬碟節點(如果未使用devfs、udev、mdev等) 。第一個使用者登陸,裝置檔案為/dev/pts/0,第二個為/dev/pts/1,以此類推。它們并不與實際實體裝置直接相關。現在大多數系統是通過此接口實作pty。

我們在X Window下打開的終端或使用telnet或ssh等方式登入Linux主機,此時均通過pty裝置。例如,如果某人在網上使用telnet程式連接配接到你的計算機上,則telnet程式就可能會打開/dev/ptmx裝置擷取一個fd。此時一個getty程式就應該運作在對應的/dev/pts/* 上。當telnet從遠端擷取了一個字元時,該字元就會通過ptmx、pts/* 傳遞給 getty程式,而getty程式就會通過pts/* 、ptmx和telnet程式往網絡上傳回“login:”字元串資訊。這樣,登入程式與telnet程式就通過“僞終端”進行通信。

telnet<—>/dev/ptmx(master)<—>pts/*(slave)<—>getty

如果一個程式把 pts/* 看作是一個串行端口裝置,則它對該端口的讀/寫操作會反映在該邏輯終端裝置對的另一個/dev/ptmx上,而/dev/ptmx則是另一個程式用于讀寫操作的邏輯裝置。

這樣,兩個程式就可以通過這種邏輯裝置進行互相交流,這很象是邏輯裝置對之間的管道操作。對于pts/* ,任何設計成使用一個串行端口裝置的程式都可以使用該邏輯裝置。但對于使用/dev/ptmx的程式,則需要專門設計來使用/dev/ptmx邏輯裝置。通過使用适當的軟體,就可以把兩個甚至多個僞終端裝置連接配接到同一個實體串行端口上。

3、 序列槽終端(/dev/ttySn)

串行端口終端(Serial PortTerminal)是使用計算機串行端口連接配接的終端裝置。計算機把每個串行端口都看作是一個字元裝置。有段時間串行端口裝置通常被稱為終端裝置,那時它的最大用途就是用來連接配接終端,是以這些串行端口所對應的裝置名稱是/dev/tts/0(或/dev/ttyS0)、/dev/tts/1(或/dev /ttyS1)等,裝置号分别是(4,0)、(4,1)等(對應于win系統下的COM1、COM2等)。若要向一個端口發送資料,可以在指令行上把标準輸出重定向到這些特殊檔案名上即可。

我們可以在指令行提示符下鍵入:echotekkaman> /dev/ttyS1會把“tekkaman”發送到連接配接在ttyS1(COM2)端口的裝置上。

在2.6以後的核心後、一些三星的晶片将序列槽終端裝置節點命名為ttySACn。TI的Omap系列晶片從2.6.37開始晶片自帶的UART裝置開始使用專有的的omap-uart驅動,故裝置節點命名為ttyOn,以差別于使用8250驅動時的裝置名“ttySn”。這其中包括筆者用到的這個S3C2440。是以我們在Uboot啟動參數中要設定console = ttySAC0才可以。這一句的意思其實就是把ttySAC0當做我們的控制台終端。

4、 其它類型終端

還針對很多不同的字元裝置存在有很多其它種類的終端裝置特殊檔案,例如針對ISDN裝置的/dev/ttyIn終端裝置等。

好了。到此為止。相信讀者已經對tty裝置有了一個概觀。

因為我們和開發闆的人機互動的接口是Windows下的序列槽控制台。這就是上面所說的控制台終端。但是我們用了console = ttySAC0.即把序列槽終端當做控制台終端。是以我們要研究具體的代碼需要cd到serial子目錄下。即序列槽終端目錄。ls顯示serial下的檔案結點。如圖所示:

嵌入式驅動解析:從序列槽驅動到Linux驅動模型

我們主要關心的是兩類檔案。一類是與體系結構和闆載資源無關的通用序列槽操作檔案。(samsung.c)一類是與體系結構相關的硬體操作檔案。(s3c2440.c s3c2410.c s5pv210.c等),我們為了得到具體的調用鍊。在具體的發送函數中加入回溯。如圖所示。

嵌入式驅動解析:從序列槽驅動到Linux驅動模型

我們得到的函數調用鍊是這樣的(以發送函數。即檔案的寫操作為例.

write->

sys_write->

vfs_write->

redirected_tty_write->

tty_write->

n_tty_write->

uart_write->

uart_start->

s3c24xx_serial_start_tx

嵌入式驅動解析:從序列槽驅動到Linux驅動模型

從具體代碼上來看。這些函數基本上都是通過結構體中的函數指針調用。我們可以把這個調用鍊分為三個部分。即tty子系統核心。tty鍊路規程。tty驅動

tty核心。是對整個tty裝置的抽象。對使用者提供統一的接口。包括sys_write->vfs_write

tty線路規程。是對傳輸資料的格式化。在tty_ldisc_N_TTY變量中描述。包括redirected_tty_write->tty_write->n_tty_write->

tty驅動。是面向tty裝置的硬體驅動。這裡面真正的對硬體進行操作。包括uart_write->uart_start->s3c24xx_serial_start_tx

這是從具體函數的角度來看的調用鍊。下面為了從資料結構的角度來分析調用鍊。介紹linux核心中針對于這一個序列槽硬體的主要資料結構。對于具體的字段我們用到的時候再解釋。

uart_driver。

就是uart驅動程式結構。封裝了tty_driver,使得底層的UART驅動無需關心tty_driver具體定義如下。

嵌入式驅動解析:從序列槽驅動到Linux驅動模型

uart_port

uart_port用于描述一個UART端口(直接對應于一個序列槽)的I/O端口或者IO記憶體位址等資訊。

嵌入式驅動解析:從序列槽驅動到Linux驅動模型

uart_ops定義了針對UART的一系列操作。注意這裡不要把uart_ops結構和uart_ops變量混淆。uart_ops結構是我們這裡的資料結構。而uart_ops變量則是一個tty_operations的變量。

在serial_core.c中定義了tty_operations的執行個體。即uart_ops變量,包含uart_open();uart_close();uart_send_xchar()等成員函數,這些函數借助uart_ops結構體中的成員函數來完成具體的操作:

嵌入式驅動解析:從序列槽驅動到Linux驅動模型

uart_ops變量是tty_operations型的一個變量。如下圖所示:

嵌入式驅動解析:從序列槽驅動到Linux驅動模型

uart_state是uart的狀态結構。

嵌入式驅動解析:從序列槽驅動到Linux驅動模型

uart_info是uart的資訊結構。在這個體系結構下定義為s3c24xx_uart_info:

嵌入式驅動解析:從序列槽驅動到Linux驅動模型

是以很顯然。用資料結構來描述函數調用鍊就是

uart_driver ->

uart_state->

uart_port->

uart_ops->

特定的函數指針。

初始化過程比較複雜。不贅述。從函數指針的調用流程為主線。忽略一些入參檢查和核心中的信号量代碼。大緻的初始化流程如下圖所示:

嵌入式驅動解析:從序列槽驅動到Linux驅動模型

打開裝置和初始化流程類似。如圖所示:

嵌入式驅動解析:從序列槽驅動到Linux驅動模型

同理資料的發送和接收如圖所示:

嵌入式驅動解析:從序列槽驅動到Linux驅動模型

這裡我們需要注意的是。使能發送并沒有真正的發送過程。而隻是使能發送中斷

這一句:enable_irq(ourport->tx_irq);

嵌入式驅動解析:從序列槽驅動到Linux驅動模型

這是因為ARM9處理器上有一個循環緩沖。使用者從write系統調用傳下來的資料就會寫入這個UTXH0寄存器。發送完事之後處理器會産生一個内部中斷。我們通過這個内部中斷就可以實作流控過程、我們打開晶片手冊可以看到如下字樣(拿ARM11舉例也一樣,。這是ARM11的):

嵌入式驅動解析:從序列槽驅動到Linux驅動模型

如下才是發送中斷的ISR(Interrupt Service Routine)中斷服務例程。一個irqreturn_t類型的handler。

嵌入式驅動解析:從序列槽驅動到Linux驅動模型

這個wr_regb(port, S3C2410_UTXH, port->x_char);就是往特定寄存器寫的過程。

至此我們的分析已經結束。相信讀者對于Linux下的tty子系統已經有一個概觀了。下面是這個uart驅動的總圖。結合資料結構的調用鍊。Linux核心完成了驅動模型和特定硬體的分離:

嵌入式驅動解析:從序列槽驅動到Linux驅動模型

序列槽驅動資料結構總圖:

嵌入式驅動解析:從序列槽驅動到Linux驅動模型

-END-

作者:Linkerist

繼續閱讀