來源:
Linux 驅動程式的 I/O, #3: kernel-space 與 user-space 的「I/O」
Linux 驅動程式的 I/O, #4: fops->ioctl 實作
重要觀念
任何作業系統底下的「驅動程式」,都需要分二個層面來討論所謂的「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->read、fops->write 與 fops->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 long arg);
˙ 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 資料的指標。撰寫程式時,要注意資料型別上的不同。
基本觀念
需要由 user-space 讀取資料,或是寫入資料給 user-space 的主要 3 個 driver method 為:read、write 與 ioctl。指向 user-space 資料空間(buffer)的指標是 kernel 回呼 driver method 時所傳遞進來的,我們由 read、write 與 ioctl 的函數原型宣告來說明如何撰寫程式:
˙int card_ioctl(struct inode *inode, struct file *filp, unsigned int cmd,
unsigned long arg);
˙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 資料的指標。撰寫程式時,要注意資料型別上的不同。
實作 fops->ioctl
ioctl 代表 input/output control 的意思,故名思義,ioctl system call 是用來控制 I/O 讀寫用的,並且是支援user application存取裝置的重要 system call。因此,在 Linux 驅動程式設計上,我們會實作 ioctl system call 以提供 user application 讀寫(input/output)裝置的功能。
依此觀念,當 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->ioctl 函數原型如下:
int ioctl(struct inode *, struct file *, unsigned int, unsigned long);
Linux 驅動程式以一個唯一且不重覆的數字來代表 ioctl 的命令,設計 Linux 驅動程式時,我們必須使用 kernel 所提供的巨集來宣告命令。根據命令的「方向」,kernel 提供以下 4 個巨集供我們宣告 ioctl 命令:,
- _IO(type,nr):表示此 ioctl 命令不指定資料向方
- _IOR(type,nr,dataitem):此 ioctl 命令由裝置 (driver) 讀取資料
- _IOW(type,nr,dataitem):此 ioctl 命令將資料寫入裝置
- _IOWR(type,nr,dataitem):此 ioctl 命令同時讀寫資料
若 user application 呼叫到驅動程式未提供的命令,則回傳 -ENOTTY 錯誤代碼。
debug card 0.1.0 範例裡,我們宣告了一個 IOCTL_WRITE 命令,當 user application 呼叫此命令後,驅動程式會將 user application 所指定的數字顯示在 debug card 上。由於我們的資料方向為「寫入裝置」,因此使用的宣告巨集為 _IOW。
Debug card 0.1.0 實作 fops->ioctl 的完整程式片斷如下:
#include <linux/ioctl.h>
#define DEV_MAJOR 121
#define DEV_NAME "debug"
#define DEV_IOCTLID 0xD0
#define IOCTL_WRITE _IOW(DEV_IOCTLID, 10, int)
unsigned long IOPort = 0x80;
void write_card(unsigned int);
void write_card(unsigned int num)
{
outb((unsigned char)num, IOPort);
}
int card_ioctl(struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg)
{
switch (cmd) {
case IOCTL_WRITE:
write_card((unsigned int)arg);
break;
default:
return -1;
}
return 0;
}
struct file_operation 的定義並未列出,不過請別忘了在 fops 裡加上 ioctl system call 的欄位。
User-space
以 debug card 0.1.0 驅動程式為例,user-space 的測試程式寫法如下:
int main(int argc, char *argv[])
{
int devfd;
int num = 0;
if (argc > 1) num = atoi(argv[1]);
if (num < 0) num = 0xff;
devfd = open("/dev/debug", O_RDONLY);
if (devfd == -1) {
printf("Can't open /dev/debug\n");
return -1;
}
printf("Write 0x%02x...\n", num);
ioctl(devfd, IOCTL_WRITE, num);
printf("Done. Wait 5 seconds...\n");
sleep(5);
close(devfd);
return 0;
}
留言列表