Linux 驅動程式的 I/O

来源:互联网 发布:bootstrap 数据库后端 编辑:程序博客网 时间:2024/06/06 00:38
 

Linux 驅動程式的 I/O, #1: 基本概念

jollen 發表於 December 12, 2006 12:02 PM

由本篇日記開始,我們將進行「Linux Device Driver 入門:I/O 處理」的議題討論。這裡所提的 I/O 處理定義是:user process 與 physical device 的 I/O 存取。

在讀「Linux Device Driver 入門:I/O 處理」專欄前,您必須熟悉 Linux 驅動程式的架構,因此「Linux Device Driver 入門:架構層」的專欄是 Jollen's Linux Device Driver 系列專欄的先備知識;此外,接下來的專欄使用的語法也必須對架構層有基本認識後才能看得懂。

Linux 驅動程式 I/O 機制

Linux device driver 處理 I/O 的「基本款」是:

  • fops->ioctl

  • fops->read

  • fops->write

另外「典藏款」則是 mmap,未來在「Linux Device Driver 進階」專欄裡再來討論這個主題。

fops->ioctl

ioctl 代表 input/output control 的意思,故名思義,ioctl system call 是用來控制 I/O 讀寫用的,並且是支援 user application 存取裝置的重要 system call。因此,在Linux驅動程式設計上,我們會實作ioctl system call以提供user application讀寫(input/output)裝置的功能。

依此觀念,回到架構篇所舉的 debug card 範例。當 user application 需要將數字顯示到 debug card 時,範例 debug card 0.1.0 便需要實作 ioctl system call,然後在 fops->ioctl 裡呼叫outb() 將 user application 所指定的數字輸出至 I/O port 80H。

User application 使用 GNU LIBC 的 ioctl() 函數呼叫device driver所提供的命令來「控制」裝置,因此驅動程式必須實作fops->ioctl 以提供「命令」給使用者。

fops->read & fops->write

read/write 是 Linux 驅動程式最重要的 2 個 driver function,也是驅動程式最核心的觀念所在。對驅動程式而言,read/write 的目的是在實作並支援 user application 的 read() 與 write() 函數;user application 是否能正常由硬體讀寫資料,完全掌握在驅動程式的 read/write ethod。

User application 呼叫 read()/write() 函數後,就會執行 fops->readfops->write。read/write method 負責讀取使用者資料與進行裝置的I/O 存取。依照觸發資料傳輸的方式來區分,我們可以將 I/O 裝置分成以下 2 種(from hardware view):

  •  Polling:I/O裝置不具備中斷。
  •  Interrupt:I/O裝置以中斷觸發方式進行I/O。

根據I/O處理原理的不同(from software view),可以將 read/write method 的實作策略分成多種排列組合來討論。為了簡化討論內容,未來的日記將鎖定「Interrupt 式的 I/O」來做探討。

--jollen

Linux 驅動程式的 I/O, #2: I/O 存取相關函數

jollen 發表於 December 20, 2006 10:57 PM

I/O 存取相關函數

要提到「I/O 處理」當然要整理 Linux 提供的相關函數,以下分 3 大類來整理:

1. I/O port
2. I/O memory
3. PCI configuration space
4. ioremap

I/O Port

以下是 Linux 提供最原始的 I/O port 存取函數:

˙ unsigned inb(unsigned port);
˙ unsigned inw(unsigned port);
˙ unsigned inl(unsigned port);
˙ void outb(unsigned char byte, unsigned port);
˙ void outw(unsigned short word, unsigned port);
˙ void outl(unsigned long word, unsigned port);

I/O Memory

以下是 Linux 提供最原始的 I/O memory 存取函數:

˙ unsigned readb(unsigned port);
˙ unsigned readw(unsigned port);
˙ unsigned readl(unsigned port);
˙ void writeb(unsigned char byte, unsigned port);
˙ void writew(unsigned short word, unsigned port);
˙ void writel(unsigned long word, unsigned port);

對於 I/O memory 的操作,Linux 也提供 memory copy 系列函數如下:

˙ memset_io(address, value, count);
˙ memcpy_fromio(dest, source, num);
˙ memcpy_toio(dest, source, num);

以上在「Linux 驅動程式觀念解析, #6: 依流程來實作 -- Physical Device Driver」介紹過一次,並且也搭配了一個簡單範例做說明,您可參考該文。

PCI Configuration Space

Linux 也提供讀寫 PCI configuration space(PCI BIOS)的函數:

˙ int pci_read_config_byte(struct pci_dev *dev, int where, u8 *val);
˙ int pci_read_config_word(struct pci_dev *dev, int where, u16 *val);
˙ int pci_read_config_dword(struct pci_dev *dev, int where, u32 *val);
˙ int pci_write_config_byte(struct pci_dev *dev, int where, u8 val);
˙ int pci_write_config_word(struct pci_dev *dev, int where, u16 val);
˙ int pci_write_config_dword(struct pci_dev *dev, int where, u32 val);

有些朋友可能看過開頭是 pcibios_* 的函數版本,「不過這是舊的函數,請勿再使用」。

ioremap()

這個 API 就重要到不行了,任何時候,Linux device driver 都「不能直接存取 physical address」。所以,「使用以上的 I/O 相關函數時,只能傳 virtual address,不能傳入 physical address」,ioremap() 就是用來將 physical address 對應到 virtual address 的 API。

小結

對於 I/O 函數的使用,應該在「深諳」Linux驅動程式架構與Linux作業系統原理的情況下使用,「單純的 kernel module + IO APIs」並不叫做 Linux 驅動程式,再更進一步的「kernel module + read/write/ioctl + IO APIs」也只是小聰明(編註),還是稱不上 Linux「驅動程式」。建構在作業系統裡的驅動程式,90% 都是在實作良好的機制與行為,因此「OS 原理與機制的研究」,才是正確的思考方向。與大家分享自己的心得,希望對您的學習有幫助。

編註:這是 Linux device driver 的「開始」但不是全部,也只是冰山一角。但是許多教育訓練機構的課程卻是以此為做為規劃方向,並不是很妥當。

--jollen

 

Linux 驅動程式的 I/O, #3: kernel-space 與 user-space 的「I/O」

jollen 發表於 December 26, 2006 11:40 PM

重要觀念

任何作業系統底下的「驅動程式」,都需要分二個層面來討論所謂的「I/O 處理」:

1. 實體層:驅動程式 v.s. 硬體。

2. 虛擬層:驅動程式 v.s. user process

在前一篇日記「Linux 驅動程式的 I/O, #2: I/O 存取相關函數」中所提到的 I/O 函數是處理「實體層」的 I/O;本日記所要介紹的copy_to_user()copy_from_user() 則是在處理「虛擬層」的 I/O。另外,在繼續往下讀之前,您必須了解以下的觀念都是「等價」的:

1. 驅動程式與 user process 的 I/O;等於

2. 驅動程式與 user process 間的 data communication;等於

3. kernel-space 與 user-space 間的 data communication。

此外,還要了解:

1. user-space 無法「直接」存取 kernel-space 的記憶體。

2. 「Linux device driver」與「user-space」間的 I/O 會與 fops->readfops->writefops->ioctl 共三個 system call 有關。

copy_to_user() 與 copy_from_user()

了解以上的觀念後,再來「直接殺進重點」就很容易懂了:從 user-space 讀取資料至 kernel-space,或是將 kernel-space 的資料寫至 user-space,「必須」透過 kernel 提供的 2 個 API 來進行。這二個 API 如下:

˙ long copy_to_user(void *to, const void *from, long n);
˙ long copy_from_user(void *to, const void *from, long n);

參數說明,以 copy_to_user() 來說:

˙ to:資料的目的位址,此參數為一個指向 user-space 記憶體的指標。
˙ from:資料的來源位址,此參數為一個指向 kernel-space 記憶體的指標。
˙ 口訣:copy data to user-space from kernel-space

copy_from_user() 來說:

˙ to:資料的目的位址,此參數為一個指向 kernel-space 記憶體的指標。
˙ from:資料的來源位址,此參數為一個指向 user-space 記憶體的指標。
˙ 口訣:copy data from user-space to kernel-space

由 user-space 讀取資料,或是寫入資料給 user-space 的 3 個 driver method 為:read、write與ioctl。

另外,指向 user-space 的指標是 kernel 回呼 driver method 時所傳遞進來的,可由 read、write 與 ioctl driver function 的函數原型宣告來觀察(紅色部份):

˙ int card_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned longarg);
˙ ssize_t write(struct file *filp, const char *buff, size_t count, loff_t *offp);
˙ ssize_t read(struct file *filp, char *buff, size_t count, loff_t *offp);

fops->ioctl 的參數 arg、fops->write 與 fops->read 的參數 buff 是指向 user-space 資料的指標。撰寫程式時,要注意資料型別上的不同。

下一篇日記再寫一個範例來配合著研究,大家應該會更清楚

 

 

 

原创粉丝点击