教你写Linux设备驱动程序:一个简短的教程
摘自:http://blog.chinaunix.net/uid-20799298-id-99675.html
原文為 Writing device driver in Linux:A brief tutorial.
該文重點給出了三個實例來講解Linux驅動,使新手快速、從代碼層了解什么是Linux設備驅動。
本文算是筆記,大體上是翻譯該文的前兩部分,即前兩個實例,這兩個例子都可能正確成功運行。
文件: Writing device drivers in Linux.pdf
大小: 216KB
下載: 下載
所需知識
- C 語言編程- 微處理器編程.對處理器的工作原理有一定的了解,如內存管理、中斷等用戶空間和內核空間
寫設備驅動時,了解“用戶空間”和“內核空間”之間的區別是非常重要的。
- 內核空間。Linux內核簡單并高效地管理著機器的硬件,為用戶提供簡單并規范的編程接口。同樣地,內核,特別是內核中的驅動,是用戶/程序員與硬件之間的橋梁或接口。內核的任何例程或函數(比如模塊、驅動)都屬于內核空間。- 用戶空間。用戶程序,比如unix shell或其他的gui應用程序(比如kpresenter),都屬于用戶空間。顯然,這些應用程序都要與硬件打交道。但是它們不并直接操作硬件,而是通過內核提供的函數來實現。用戶空間與內核空間之間的接口函數
內核為用戶空間提供了一系列的例程或函數,用戶的應用程序利用這些接口來與硬件交互
。通常,在UNIX或Linux系統中,這種對話是通過函數或子程序來讀寫文件的。原因是從
用戶的角度來看,UNIX設備就是文件。
另一方面,在內核空間中Linux也提供了一系列的函數或子程序來完成底層與硬件的交互
,并允許從內核向用戶空間傳遞信息。
通常,每個用戶空間的(設備或文件允許使用的)函數,都能在內核空間中找到一個類似
的函數,(允許信息從內核傳遞給用戶空間,反之亦然)
內核空間與硬件設備之間的接口函數
內核空間中有許多函數用于控制硬件或在內核與硬件之間交互信息。
第一個驅動:在用戶空間加載和移除驅動
現在將展示如何完成第一個驅動,在內核中將看作模塊
新建一個文件nothing.c如下
include
MODULE_LICENSE(“Dual BSD/GPL”);
2.6.x版本后的內核,編譯模塊會略微復雜一點。首先,需要有一份完整的、編譯過的內
核源碼樹。在下面的文字中,將假設使用2。6。8版本的內核。
其次,需要一個makefile文件,本例中的makefile文件名為Makefile,內容如下:
obj-m := nothing.o
與之前版本的內核不同,現在編譯模塊時使用的內核需要與模塊將要加載的內核相同。
編譯上面的文件,可以使用命令:
make -C /usr/src/kernel-source-2.6.8 M=pwd modules
這個極其簡單的模塊就屬于內核空間,一旦其被加載,它就是內核空間的一部分。
在用戶空間,可以使用下面的命令加載它,需要root權限:
insmod nothing.ko
insmod 這個命令用于為內核加載模塊。盡管現在我們已經加載了nothing.ko這個模塊,
但是這個模塊畢竟沒有任何用處。
可以通過查看系統里已加載的模塊來檢查是否已經成功加載了nothing.ko
lsmod
最后,需要卸載該模塊時使用下面的命令:
rmmod nothing
重新使用lsmod,可以發現nothing模塊已經不在了。
“Hello world”驅動:在內核空間加載和移除驅動
當一個模塊設備驅動加載到內核,將執行一些初始的工作,如重新設置設備,reserving
RAM, reserving interrupts, reserving input/output ports, etc.
這些工作得以在內核空間執行,必須要有兩個函數存在:module_init 和
module_exit;它們對應于用戶空間的insmod和rmmod命令。總之,用戶命令insmod和
rmmod使用了內核空間的函數module_init和module_exit.
來看一個經典的程序 HELLO WORLD:
//hello.c #include #include #inlucde MODULE_LICENSE("Dual BSD/GPL");static int hello_init(void) {printk("<1> Hello world!\n");return 0; }static void hello_exit(void) {printk("<1> Bye, cruel world!\n"); }module_init(hello_init); module_exit(hello_exit);其中hello_init 和 hello_exit 函數可以取任意名,但為了加載和移除功能是更容易識
別,它們作為參數傳遞給函數module_init 和 module_exit.
printk函數與printf函數非常類似,但printk只工作在內核中。<1>表示打印信息
為最高優先級(數字越低,優先級越高)。這樣,不僅可以在內核系統日志中看到該
打印信息,還能在系統控制臺接收到該打印信息。
可以用之前的命令來編譯這個模塊,此時只需要將模塊名加入到Makefile文件中即可:
obj-m := nothing.o hello.o
本文的其他部分,將Makefile作為給讀者的練習。一個完整的Makefile文件可以編譯
本教程中的所有示例模塊。
當模塊被加載或卸載時,通過printk打印的信息將會出現在系統控制臺。如果打印
信息沒有出現在終端里,則可通過dmesg命令或查看系統日志文件(cat
var/log/syslog)看到打印信息。
一個完整的驅動“memory“:此驅動的初始部分
接著將介紹如何構建一個完整的設備驅動:memory.c??梢詮脑撛O備中讀取一個字符,
也可向其寫入一個字符。這個設備并沒有實際意義,只是因為它是一個完整的
驅動程序,遂將其作為一個實例來說明。它很容易實現,因為它并不是一個正真的
硬件設備的接口(除了電腦本身)。
這個驅動中,要添加幾個在設備驅動程序中頻繁出現的#inclu#include
include
include
include
include
include
include
include
include
include
include
MODULE_LICENSE(“Dual BSD/GPL”);
int memory_open(struct inode *inode, struct file *filp);
int memory_release(struct inode *inode, struct file *filp);
ssize_t memory_read(struct file *filp, char *buf, size_t count, loff_t
*f_pos);
ssize_t memory_write(struct file *filp, char *buf, size_t count, loff_t
*f_pos);
void memory_exit(void);
int memory_init(void);
struct file_operations memory_fops = {
read: memory_read,
write: memory_write,
open: memory_open,
release: memory_release
};
module_init(memory_init);
module_exit(memory_exit);
int memory_major = 60;r *memory_buffer;件之后,聲明了幾個后面要定義的函數。在file_operations結構的定義中聲明了
幾個通常用來操作文件的函數。這些在后面會詳細介紹到。接著,向內核聲明初始化和
退出函數-加載和卸載模塊時使用的。最后,聲明驅動的全局變量:memory_major表示驅
動的主驅動號,memory_buffer指向一塊用于存儲驅動數據的內存區域。
“memory”驅動:設備與其文件的連接
在UNIX和Linux中,從用戶空間訪問設備與訪問文件相同。這些設備文件通常位于/dev目
錄下。
將一個普通文件與設備文件關聯起來需要使用兩個數字:major number 和 minor
number。內核使用major number將一個文件鏈接到它的驅動。而minor number是供設備內
部使用。
要做到這一點,一個文件(將用于訪問設備驅動程序)的創建必須使用root身份鍵入以下
命令:
mknod /dev/memory c 60 0
上面這句命令中,c表示創建一個字符設備,該設備的主驅動號major number為60,次驅
動號minor number為0。
對于這個驅動,為了在內核空間將其鏈接到對應的/dev下的文件,需要使用
register_chrdev函數。調用該函數使用到三個參數:major number,
一個字符串用于表示該模塊的名字,一個file_operations結構。
在安裝模塊時它以下面的方式被調用:
int memory_init(void)
{
int result;
fail:
memory_exit();
return result;
}
注意kmalloc函數的使用。這個函數在內核空間中分配一塊用于設備驅動緩沖區的內存。
它的使用方法與著名的malloc函數類似。最后,如果注冊主驅動號失敗或分配內存失敗,
這個模塊也將失敗。
“memory”驅動:移除驅動
為了在memory_exit函數中移除模塊,需要使用到unregsiter_chrdev函數。它將為內核
釋放相應的主驅
void memory_exit(void)
{
unregister_chrdev(memory_major, “memory”);
}移除驅動時還原一個干凈的內核,在這個函數中同時釋放了驅動的緩沖區。
“memory”驅動:像打開文件一樣打開設備
內核空間中與用戶空間中打開文件(fopen)相對應的是open:調用register_chrdev時
使用到了一個file_operations結構,而open正是這個結構的成員。open函數的參數有:
一個inode結構,它向內核傳遞有關主驅動號major number和次驅動號minor number的相
關信息;一個file結構,該結構中包括操作文件的多個不同函數。但本文并不對這些函數
作詳細介紹。
當一個文件被打開,通常需要初始化驅動變量或重新設置這個設備。但在這個例子中這
些沒有做這些工作。
memory_open函數如下:
int memory_open(struct inode *inode, struct file *filp)
{
return 0;
}
“memory”驅動:像關閉文件一樣關閉設備
與用戶空間中關閉文件(fclose)相對應的是release:調用register_chrdev時使用到一
個file_operations結構,release正是該結構的成員。在本例中,它對應
memory_release函數,與前面類似,它也有兩個參數:inode結構和file結構。
當一個文件關閉,通常需要釋放已使用的內存和任何打開文件時關鏈到的變量。但是,同
樣的因為本例十分簡單,這些工作這里都沒有做。
memory_release函數如下:
int memory_release(struct inode *inode, struct file *filp)
{
return 0;
}
“memory”驅動:讀設備
同樣,對應于用戶空間讀文件的fread,這里用到read:它也是file_operations結構的成
員。這里它對應memory_read函數。它的參數有:一個file結構;一個緩沖區buf,用戶
空間從該緩沖區中讀數據;一個計數器count記錄傳輸的字節;最后,還有f_pos,用來
指示從文件的哪里開始讀取。
在本例中,memory_read函數使用函數copy_to_user從驅動緩沖區中發送一個字節給用戶
ssize_t memory_read(struct *file filp, char *buf,
size_t count, loff_t *f_pos)
{
copy_to_user(buf, memory_buffer, 1);
}置f_pos同時也會改變。如果從文件開頭讀起,f_pos會以1遞增,并且返回已正確
讀到的字節數,即1。如果不是從文件開頭讀起,文件的結束標志0將被返回,因為文件中
沒有數據。
“memory”驅動:寫設備
與fwrite類似,內核空間有write:它是file_operations結構的成員。本例中為
memory_write,有下面幾個參數:一個file結構;buf緩沖區,供用戶空間寫入;count,
計數器記錄寫入數據的字節數;f_pos,寫入的ssize_t memory_write(struct file *filp, char *buf,
size_t count, loff_t *f_pos)
{
char *tmp;
}
copy_from_user將數據從用戶空間傳送到內核空間。
完整的“memory“驅動
加入之前的所有代碼后,便組成了完整的memory驅動memory.c:
在使用本驅動之前,當然需要先編譯它,方法與前面類似。加載模塊:
insmod memory.ko
很方便即可取消對設備的保護:
chmod 666 /dev/memory
如果一切順利,將有一個設備/dev/memory存在,且可以將其中寫字符串或字符,它將存
儲字符串或多個字符中的最后一個。可以像這樣來操作:
echo -n abcdef > /dev/memory
使用cat來檢查這個設備的內容:
cat /dev/memory
已存的字符不會改變,除非再寫入覆蓋它或這個模塊被卸載。
附:
實例2 memory 驅動實驗:
代碼 memory.c
#include <linux/init.h> //#include #include <linux/module.h> #include <linux/kernel.h> #include <linux/slab.h> #include <linux/fs.h> #include <linux/errno.h> #include <linux/types.h> #include <linux/proc_fs.h> #include <linux/fcntl.h> //#include #include <linux/uaccess.h>MODULE_LICENSE("Dual BSD/GPL");int memory_open(struct inode *inode, struct file *filp); int memory_release(struct inode *inode, struct file *filp); ssize_t memory_read(struct file *filp, char *buf, size_t count, loff_t *f_pos); ssize_t memory_write(struct file *filp, char *buf, size_t count, loff_t *f_pos); void memory_exit(void); int memory_init(void);struct file_operations memory_fops = {read:memory_read,write:memory_write,open:memory_open,release:memory_release };module_init(memory_init); module_exit(memory_exit);int memory_major = 60;char *memory_buffer;int memory_init(void) {int result;result = register_chrdev(memory_major, "memory", &memory_fops);if (result < 0) {printk("<1>memory: can't obtain major number %d\n", memory_major);return result;}memory_buffer = kmalloc(1, GFP_KERNEL);if (!memory_buffer) {result = - ENOMEM;goto fail;}memset(memory_buffer, 0, 1);printk("<1>Inserting memory module\n");return 0;fail:memory_exit();return result; }void memory_exit(void) {unregister_chrdev(memory_major, "memory");if (memory_buffer)kfree(memory_buffer);printk("<1>Removing memory module\n"); }int memory_open(struct inode *inode, struct file *filp) {return 0; }int memory_release(struct inode *inode, struct file *filp) {return 0; }ssize_t memory_read(struct file *filp, char *buf, size_t count, loff_t *f_pos) {copy_to_user(buf, memory_buffer, 1);if (*f_pos == 0) {*f_pos += 1;return 1;} elsereturn 0; }ssize_t memory_write(struct file *filp, char *buf, size_t count, loff_t *f_pos) {char *tmp;tmp = buf + count - 1;copy_from_user(memory_buffer, tmp, 1);return 1; }Makefile:
obj-m := memory.o
KERNELDIR := /lib/modules/(shelluname?r)/buildPWD:=(shell pwd)
modules:
(MAKE)?C(KERNELDIR) M=$(PWD) modules
編譯:make
生成文件中有 memory.ko, 該文件即要使用的目標模塊
加載:sudo insmod ./memory.ko
查看dmesg信息:dmesg | tail -n 1
[10911.945739] Inserting memory module
改變操作設備文件權限:sudo chmod 666 /dev/memory
向驅動中寫入數據:echo -n abcdefg > /dev/memory
查看驅動中保存的數據:
[linux@ ~]cat/dev/memoryg[linux@?]
可見其為最后寫入的數據。
卸載驅動:
[linux@ ~]sudormmodmemory[linux@?] dmesg | tail -n 2
[10911.945739] Inserting memory module
[11155.809076] Removing memory module
—————————-實驗完畢
分析
上面代碼中主要有五個函數重點注意下:
register_chrdev
unregister_chrdev
copy_to_user
copy_from_user
kmalloc
/*
* 成功:返回0
* 失敗:-EINVAL表示申請的主設備號非法(可能是主設備號大于最大設備號)
* -EBUSY 表示所申請的主設備號已為其它設備使用
* 如果動態分配成功,則此函數將返回主設備號
*
*/
static inline int register_chrdev(
unsigned int major, //設備驅動向內核申請主設備號,若為0則系統動態分配一個主設備號
const char *name, //設備名
const struct file_operations *fops //各調用的入口點
);
static inline void unregister_chrdev(
unsigned int major,
const char *name
);
arch/x86/lib/usercopy_32.c
/**
* copy_to_user: - Copy a block of data into user space.
* @to: Destination address, in user space.
* @from: Source address, in kernel space.
* @n: Number of bytes to copy.
*
* Context: User context only. This function may sleep.
*
* Copy data from kernel space to user space.
*
* Returns number of bytes that could not be copied.
* On success, this will be zero.
*/
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);
/**
* copy_from_user: - Copy a block of data from user space.
* @to: Destination address, in kernel space.
* @from: Source address, in user space.
* @n: Number of bytes to copy.
*
* Context: User context only. This function may sleep.
*
* Copy data from user space to kernel space.
*
* Returns number of bytes that could not be copied.
* On success, this will be zero.
*
* If some data could not be copied, this function will pad the copied
* data to the requested size using zero bytes.
*/
unsigned long _copy_from_user(void *to, const void __user *from, unsigned long n);
在內核中動態開辟內存
void *kmalloc(size_t size, int flags);
size:要分配內存的大小
flags:分配標志,以幾個方式控制kmalloc的行為
總結
以上是生活随笔為你收集整理的教你写Linux设备驱动程序:一个简短的教程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux卸载设备驱动命令,Linux设
- 下一篇: 搭建测试环境如何配置软件,软件测试环境的