欧美极品高清xxxxhd,国产日产欧美最新,无码AV国产东京热AV无码,国产精品人与动性XXX,国产传媒亚洲综合一区二区,四库影院永久国产精品,毛片免费免费高清视频,福利所导航夜趣136

 找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

搜索
查看: 2050|回復(fù): 0
打印 上一主題 下一主題
收起左側(cè)

Linux系統(tǒng)的硬件驅(qū)動程序編寫原理

[復(fù)制鏈接]
跳轉(zhuǎn)到指定樓層
樓主
ID:107189 發(fā)表于 2016-3-5 17:38 | 只看該作者 回帖獎勵 |倒序?yàn)g覽 |閱讀模式
顧名思義,驅(qū)動程序是用來控制計算機(jī)外圍設(shè)備的,Linux系統(tǒng)將所有的外圍設(shè)備都高度地抽象成一些字節(jié)的序列,并且以文件的形式來表示這些設(shè)備。我們可以來看一下Linux的I/O子系統(tǒng)(圖1)。

從圖上我們可以看出,內(nèi)核緊緊地包圍在硬件周圍,內(nèi)核是一些軟件包的組合,它們可以直接訪問系統(tǒng)的硬件,包括處理器、內(nèi)存和I/O設(shè)備。而用戶進(jìn)程則通過內(nèi)核提供的用戶服務(wù)來和內(nèi)核通訊,從而間接地控制系統(tǒng)硬件。
我們可以通過圖2來了解這些動作的具體情況。

圖上顯示了用戶級的程序使用內(nèi)核提供的標(biāo)準(zhǔn)系統(tǒng)調(diào)用來與內(nèi)核通訊,這些系統(tǒng)調(diào)用有:open(), read(), write(), ioctl(), close() 等等。
 Linux的內(nèi)核是一個有機(jī)的整體。每一個用戶進(jìn)程運(yùn)行時都好像有一份內(nèi)核的拷貝,每當(dāng)用戶進(jìn)程使用系統(tǒng)調(diào)用時,都自動地將運(yùn)行模式從用戶級轉(zhuǎn)為內(nèi)核級,此時進(jìn)程在內(nèi)核的地址空間中運(yùn)行。

 Linux內(nèi)核使用"設(shè)備無關(guān)"的I/O子系統(tǒng)來為所有的設(shè)備服務(wù)。每個設(shè)備都提供標(biāo)準(zhǔn)接口給內(nèi)核,從而盡可能地隱藏了自己的特性。圖3展示了用戶程序使用一些基本的系統(tǒng)調(diào)用從設(shè)備讀取數(shù)據(jù)并且將它們存入緩沖的例子。我們可以看到,每當(dāng)一個系統(tǒng)調(diào)用被使用時,內(nèi)核就轉(zhuǎn)到相應(yīng)的設(shè)備驅(qū)動例程來操縱硬件。

  每個設(shè)備在Linux系統(tǒng)上看起來都像一個文件,它們存放在/dev目錄中并被稱為"特殊文件"或是"設(shè)備節(jié)點(diǎn)"。大家可以使用ls -l /dev/lp* 來得到以下的輸出:

  crw-rw-rw 1 root root 6, 0 April 23 1994 /dev/lp0

  這行輸出表示lp0是一個字符設(shè)備(屬性字段的第一個字符是'c'),主設(shè)備號是6,次設(shè)備號是0。主設(shè)備號用來向內(nèi)核表明這一設(shè)備節(jié)點(diǎn)所代表的驅(qū)動程序的類型(比如:主設(shè)備號是3的塊設(shè)備是IDE磁盤驅(qū)動程序,而主設(shè)備號為8的塊設(shè)備是SCSI磁盤驅(qū)動程序);每個驅(qū)動程序負(fù)責(zé)管理它所驅(qū)動的幾個硬件實(shí)例,這些硬件實(shí)例則由次設(shè)備號來表示(例如:次設(shè)備號為0的SCSI磁盤代表整個也可以說是"第一個"SCSI磁盤,而次設(shè)備號為1到15的磁盤代表此SCSI磁盤上的15個分區(qū))。
設(shè)備驅(qū)動程序是一組由內(nèi)核中的相關(guān)子例程和數(shù)據(jù)組成的I/O設(shè)備軟件接口。每當(dāng)內(nèi)核意識到要對某個設(shè)備進(jìn)行特殊的操作時,它就調(diào)用相應(yīng)的驅(qū)動例程。這就使得控制從用戶進(jìn)程轉(zhuǎn)移到了驅(qū)動例程,當(dāng)驅(qū)動例程完成后,控制又被返回至用戶進(jìn)程。圖5就顯示了以上的過程。

每個設(shè)備驅(qū)動程序都具有以下幾個特性:

  l 具有一整套的和硬件設(shè)備通訊的例程,并且提供給操作系統(tǒng)一套標(biāo)準(zhǔn)的軟件接口;

  l 具有一個可以被操作系統(tǒng)動態(tài)地調(diào)用和移除的自包含組件;

  l 可以控制和管理用戶程序和物理設(shè)備之間的數(shù)據(jù)流。

  接下來我們來了解一下字符設(shè)備和塊設(shè)備,它們是Linux系統(tǒng)中兩種主要的外圍設(shè)備。我們常見的磁盤是塊設(shè)備,而終端和打印機(jī)是字符設(shè)備。塊設(shè)備被用戶程序通過系統(tǒng)緩沖來訪問。特別是系統(tǒng)內(nèi)存分配和管理進(jìn)程就沒有必要來充當(dāng)從外設(shè)讀寫的數(shù)據(jù)傳輸者了。正好與之相反的是,字符設(shè)備直接與用戶程序進(jìn)行通訊,而且兩者似乎沒有緩沖區(qū)。Linux的傳輸控制機(jī)制會根據(jù)用戶程序的需要來正確地操縱內(nèi)存和磁盤等外設(shè)來取得數(shù)據(jù)。在Linux系統(tǒng)中字符設(shè)備驅(qū)動器被保存為/usr/src/linux/drivers/char目錄中。下面我們重點(diǎn)介紹字符設(shè)備驅(qū)動程序的開發(fā)方法。
首先了解一下Linux的內(nèi)核編程環(huán)境。我們知道每個Linux用戶進(jìn)程都在一個獨(dú)立的系統(tǒng)空間中運(yùn)行著,與系統(tǒng)區(qū)和其他用戶進(jìn)程相隔離。這樣就保護(hù)了一個用戶進(jìn)程的運(yùn)行環(huán)境,以免被其他用戶進(jìn)程所破壞。與這種情況正相反的是,設(shè)備驅(qū)動程序運(yùn)行在內(nèi)核模式,它們具有很大的自由度。這些設(shè)備驅(qū)動程序都是被假設(shè)為正確和可靠的,它們是內(nèi)核的一部分,可以處理系統(tǒng)中斷請求和訪問外圍設(shè)備,同時它們有效地處理中斷請求以便系統(tǒng)調(diào)度程序保持系統(tǒng)需求的平衡。所以設(shè)備驅(qū)動程序可以脫離系統(tǒng)的限制來使用系統(tǒng)區(qū),比如系統(tǒng)的緩沖區(qū)等等。

  一個設(shè)備驅(qū)動程序同時包括中斷和同步區(qū)域。其中中斷區(qū)域處理實(shí)時事件并且被設(shè)備的中斷所驅(qū)動;而同步區(qū)域則組成了設(shè)備的剩余部分,處理進(jìn)程的同步事件。所以,當(dāng)一個設(shè)備需要一些軟件服務(wù)時,就發(fā)出一個"中斷",然后中斷處理器得到產(chǎn)生中斷的原因同時進(jìn)行相應(yīng)的動作。

  一個Linux進(jìn)程可能會在事件發(fā)生之前一直等待下去。例如,一個進(jìn)程可能會在運(yùn)行中等待一些寫入硬件設(shè)備的信息的到來。其中一種方式是進(jìn)程可以使用sleep()和wakeup()這兩個系統(tǒng)調(diào)用,進(jìn)程先使自己處于睡眠狀態(tài),等待事件的到來,一旦事件發(fā)生,進(jìn)程即可被喚醒。舉個例子來說:interruptible_sleep_on(&dev_wait_queue)函數(shù)使進(jìn)程睡眠并且將此進(jìn)程的進(jìn)程號加到進(jìn)程睡眠列表dev_wait_queue中,一旦設(shè)備準(zhǔn)備好后,設(shè)備發(fā)出一個中斷,從而導(dǎo)致設(shè)備驅(qū)動程序中相應(yīng)的例程被調(diào)用,這個驅(qū)動程序例程處理完一些設(shè)備要求的事宜后會發(fā)出一個喚醒進(jìn)程的信號,通常使用wake_up_interruptible(&dev_wait_queue)函數(shù),它可以喚醒dev_wait_queue所示列表中的所有進(jìn)程。

  特別要注意的是,如果兩個和兩個以上的進(jìn)程共享一些公共數(shù)據(jù)區(qū)時,我們必須將之視為臨界區(qū),臨界區(qū)保證了進(jìn)程間互斥地訪問公共數(shù)據(jù)。在Linux系統(tǒng)中我們可以使用cli()和sti()兩個內(nèi)核例程來處理這種互斥,當(dāng)一個進(jìn)程在訪問臨界區(qū)時可以使用cli()來關(guān)閉中斷,離開時則使用sti()再將中斷打開,就像下面的寫法:

  cli()

   臨界區(qū)

  sti()

除了以上這些,我們還得了解一下虛擬文件系統(tǒng)交換(VFS)的概念。

圖6中的"文件操作結(jié)構(gòu)"在/usr/include/linux/fs.h文件中定義,此結(jié)構(gòu)包含了驅(qū)動程序中的函數(shù)列表。圖上的初始化例程xxx_init()根據(jù)VFS和設(shè)備的主設(shè)備號來注冊"文件操作結(jié)構(gòu)"。
下面是一些設(shè)備驅(qū)動程序的支撐函數(shù)(具體使用方法詳見Linux編程手冊,使用man命令):

  add_timer()

  定時間一過,可以引發(fā)函數(shù)的執(zhí)行;

  cli()

  關(guān)閉中斷,阻止中斷的捕獲;

  end_request()

  當(dāng)一個請求被完成或被撤銷時被執(zhí)行;

  free_irq()

  釋放一個先前被request_irq()和irqaction()捕獲的的中斷請求;

  get_fs*()

  允許一個設(shè)備驅(qū)動程序訪問用戶區(qū)數(shù)據(jù)(一塊不屬于內(nèi)核的內(nèi)存區(qū));

  inb(), inb_p()

  從一個端口讀取一個字節(jié),其中inb_p() 會一直阻塞直到從端口得到字節(jié)為止;

  irqaction()

  注冊一個中斷;

  IS_*(inode)

  測試inode是否在一個被mount了的文件系統(tǒng)上;

  kfree*()

  放先前被kmalloc()分配的內(nèi)存區(qū);

  kmalloc()

  分配大于4096個字節(jié)的大塊內(nèi)存區(qū);

  MAJOR()

  返回設(shè)備的主設(shè)備號;

  MINOR()

  返回設(shè)備的次設(shè)備號;

  memcpy_*fs()

  在用戶區(qū)和內(nèi)核區(qū)之間復(fù)制大塊的內(nèi)存;

  outb(), outb_p()

  向一個端口寫一個字節(jié),其中outb_p()一直阻塞直到寫字節(jié)成功為止;

  printk()

  內(nèi)核使用的printf()版本;

  put_fs*()

  允許設(shè)備驅(qū)動程序?qū)?shù)據(jù)寫入用戶區(qū);

  register_*dev()

  在內(nèi)核中注冊一個設(shè)備;

  request_irq()

  向內(nèi)核申請一個中斷請求IRQ,如果成功則安裝一個中斷請求處理器;

  select_wait()

  將一個進(jìn)程加到相應(yīng)select_wait隊(duì)列中;

  *sleep_on()

  使進(jìn)程睡眠以等待事件的到來,并且將wait_queue 入口點(diǎn)加到列表中以便事件到來時將進(jìn)程喚醒;

  sti()

  和cti()相對應(yīng),恢復(fù)中斷捕獲;

  sys_get*()

  系統(tǒng)調(diào)用,得到進(jìn)程的有關(guān)信息;

  wake_up*()

  喚醒先前被*sleep_on() 睡眠的進(jìn)程
 Linux的用戶進(jìn)程不能直接訪問系統(tǒng)物理內(nèi)存。每個用戶進(jìn)程都有自己的內(nèi)存空間(用戶虛擬地址空間,開始于虛擬0地址)。同樣內(nèi)核也具有自己特定的內(nèi)存空間--系統(tǒng)虛擬地址空間。每當(dāng)用戶使用系統(tǒng)調(diào)用read()或write()時,設(shè)備驅(qū)動程序就在內(nèi)核地址空間和用戶程序地址空間之間拷貝數(shù)據(jù)。許多Linux例程,比如memcpy_*fs() 和 put_fs*()可以使設(shè)備驅(qū)動程序穿越"用戶-系統(tǒng)"邊界來傳輸數(shù)據(jù)。而且數(shù)據(jù)可以是字節(jié)、字或任意長度的數(shù)據(jù)塊。例如,memcpy_fromfs()可以從用戶內(nèi)存空間傳輸任意長度的數(shù)據(jù)塊到設(shè)備,而get_fs_byte()則只從用戶內(nèi)存空間傳輸一個字節(jié);相同的memcpy_tofs()和 put_fs_byte()也是如此,只不過它們是寫數(shù)據(jù)到用戶內(nèi)存空間。

  然而,在內(nèi)核可訪問內(nèi)存空間和設(shè)備本身之間傳輸數(shù)據(jù)則要視不同的計算機(jī)而定。一些計算機(jī)需要使用一些特殊CPU輸入輸出指令來完成這項(xiàng)工作,這通常被稱為DMA(直接內(nèi)存訪問)。而另一種方案則是使用內(nèi)存映射I/O來解決,通常使用系統(tǒng)提供的I/O函數(shù),比如inb()和outb()來分別地從I/O地址(即端口)讀取和向I/O地址輸出一單字節(jié),可以使用以下的語句:

  unsigned char inb(int port)

  outb(char data, int port)

好,下面就可以來看看字符設(shè)備驅(qū)動程序的基本結(jié)構(gòu)。如圖6所示xxx_write()例程輪詢設(shè)備是否已經(jīng)準(zhǔn)備好接收數(shù)據(jù),如果準(zhǔn)備好了,則將指定長度的字符串從用戶內(nèi)存空間發(fā)送到字符設(shè)備。另外還可以使用中斷來通知設(shè)備是否準(zhǔn)備好,這樣就不需要程序?yàn)榱溯喸兌却,從而提高CPU的利用率。xxx_table[]是一個結(jié)構(gòu)的數(shù)組,它包含很多成員變量,包括xxx_wait_queue和bytes_xfered(兩者都被用于讀寫操作)。xxx_open()使用request_irq()或irqaction()來調(diào)用xxx_interrupt()例程。

  為了使設(shè)備驅(qū)動程序被正確地初始化,每當(dāng)系統(tǒng)啟動時,xxx_init() 例程必須被調(diào)用。為了確保這一操作,需要將語句mem_start = xxx_init(mem_start); 加到/usr/src/linux/driver/char/mem.c文件的chr_drv_init()函數(shù)末。接下來的工作就是將驅(qū)動設(shè)備安裝到內(nèi)核中去了(注意:字符設(shè)備驅(qū)動程序只能被安裝在/usr/src/linux/drivers/char/char.a庫文件中)。

分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友
收藏收藏 分享淘帖 頂 踩
回復(fù)

使用道具 舉報

您需要登錄后才可以回帖 登錄 | 立即注冊

本版積分規(guī)則

小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術(shù)交流QQ群281945664

Powered by 單片機(jī)教程網(wǎng)

快速回復(fù) 返回頂部 返回列表