字元裝置驅動(一)框架
目錄
title: 字元裝置驅動(一)框架
tags: linux
date: 2018-11-19 22:40:11
toc: true
---
字元裝置驅動(一)框架
命令速記
mknod file c major cnt #建立字元裝置 rmmod file #解除安裝驅動 insmod file #安裝驅動 lsmod #檢視安裝的驅動 mount -t nfs -o nolock 192.168.5.222:/home/book/stu /mnt #掛載nfs mount -o nolock,rsize=1024,wsize=1024 192.168.137.222:/home/book/stu /mnt ifconfig eth0 192.168.5.200 showmount -e #檢視主機允許的掛載
框架結構
Linux
中App
通過呼叫open/close
等庫函式去控制硬體裝置.
open,write,read
這些是系統的介面函式,由C庫實現- 呼叫C庫的這些函式時,會觸發
swi x
指令,引發異常,進入異常處理函式稱為system call interface
system call interface
會去呼叫system open/write/read
----稱為virtual Filesystem 虛擬檔案系統system open/write/read
根據不同的檔案呼叫不同的驅動程式
資料結構
- 驅動程式建立了按照
字元裝置屬性+主裝置號+次裝置號
- 需要有一個裝置檔案,他的屬性是
字元裝置+驅動對應的主裝置號
- app操作這個裝置檔案,獲取其屬性,操作裝置檔案時,核心呼叫相應的驅動程式介面
- 注意app操作的裝置的屬性一定是要等同於驅動要求的裝置屬性,否則依然是無法找到裝置的
介面實現
App
使用open
等函式呼叫驅動,open
這一類函式是定義在fs.h
中的struct file_operations
,這是一個統一的介面.所以一個驅動程式需要按照這個格式提供相應的介面即可
*
* NOTE:
* read, write, poll, fsync, readv, writev, unlocked_ioctl and compat_ioctl
* can be called without the big kernel lock held in all filesystems.
*/
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*dir_notify)(struct file *filp, unsigned long arg);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
};
struct module *owner
第一個 file_operations 成員根本不是一個操作; 它是一個指向擁有這個結構的模組的指標. 這個成員用來在它的操作還在被使用時阻止模組被解除安裝. 幾乎所有時間中, 它被簡單初始化為 THIS_MODULE, 一個在 <linux/module.h> 中定義的巨集.
loff_t (*llseek) (struct file *, loff_t, int);
llseek 方法用作改變檔案中的當前讀/寫位置, 並且新位置作為(正的)返回值. loff_t 引數是一個"long offset", 並且就算在 32位平臺上也至少 64 位寬. 錯誤由一個負返回值指示. 如果這個函式指標是 NULL, seek 呼叫會以潛在地無法預知的方式修改 file 結構中的位置計數器( 在"file 結構" 一節中描述).
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
用來從裝置中獲取資料. 在這個位置的一個空指標導致 read 系統呼叫以 -EINVAL("Invalid argument") 失敗. 一個非負返回值代表了成功讀取的位元組數( 返回值是一個 "signed size" 型別, 常常是目標平臺本地的整數型別).
ssize_t (*aio_read)(struct kiocb *, char __user *, size_t, loff_t);
初始化一個非同步讀 -- 可能在函式返回前不結束的讀操作. 如果這個方法是 NULL, 所有的操作會由 read 代替進行(同步地).
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
傳送資料給裝置. 如果 NULL, -EINVAL 返回給呼叫 write 系統呼叫的程式. 如果非負, 返回值代表成功寫的位元組數.
ssize_t (*aio_write)(struct kiocb *, const char __user *, size_t, loff_t *);
初始化裝置上的一個非同步寫.
int (*readdir) (struct file *, void *, filldir_t);
對於裝置檔案這個成員應當為 NULL; 它用來讀取目錄, 並且僅對檔案系統有用.
unsigned int (*poll) (struct file *, struct poll_table_struct *);
poll 方法是 3 個系統呼叫的後端: poll, epoll, 和 select, 都用作查詢對一個或多個檔案描述符的讀或寫是否會阻塞. poll 方法應當返回一個位掩碼指示是否非阻塞的讀或寫是可能的, 並且, 可能地, 提供給核心資訊用來使呼叫程序睡眠直到 I/O 變為可能. 如果一個驅動的 poll 方法為 NULL, 裝置假定為不阻塞地可讀可寫.
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
ioctl 系統呼叫提供了發出裝置特定命令的方法(例如格式化軟盤的一個磁軌, 這不是讀也不是寫). 另外, 幾個 ioctl 命令被核心識別而不必引用 fops 表. 如果裝置不提供 ioctl 方法, 對於任何未事先定義的請求(-ENOTTY, "裝置無這樣的 ioctl"), 系統呼叫返回一個錯誤.
int (*mmap) (struct file *, struct vm_area_struct *);
mmap 用來請求將裝置記憶體對映到程序的地址空間. 如果這個方法是 NULL, mmap 系統呼叫返回 -ENODEV.
int (*open) (struct inode *, struct file *);
儘管這常常是對裝置檔案進行的第一個操作, 不要求驅動宣告一個對應的方法. 如果這個項是 NULL, 裝置開啟一直成功, 但是你的驅動不會得到通知.
int (*flush) (struct file *);
flush 操作在程序關閉它的裝置檔案描述符的拷貝時呼叫; 它應當執行(並且等待)裝置的任何未完成的操作. 這個必須不要和使用者查詢請求的 fsync 操作混淆了. 當前, flush 在很少驅動中使用; SCSI 磁帶驅動使用它, 例如, 為確保所有寫的資料在裝置關閉前寫到磁帶上. 如果 flush 為 NULL, 核心簡單地忽略使用者應用程式的請求.
int (*release) (struct inode *, struct file *);
在檔案結構被釋放時引用這個操作. 如同 open, release 可以為 NULL.
int (*fsync) (struct file *, struct dentry *, int);
這個方法是 fsync 系統呼叫的後端, 使用者呼叫來重新整理任何掛著的資料. 如果這個指標是 NULL, 系統呼叫返回 -EINVAL.
int (*aio_fsync)(struct kiocb *, int);
這是 fsync 方法的非同步版本.
int (*fasync) (int, struct file *, int);
這個操作用來通知裝置它的 FASYNC 標誌的改變. 非同步通知是一個高階的主題, 在第 6 章中描述. 這個成員可以是NULL 如果驅動不支援非同步通知.
int (*lock) (struct file *, int, struct file_lock *);
lock 方法用來實現檔案加鎖; 加鎖對常規檔案是必不可少的特性, 但是裝置驅動幾乎從不實現它.
ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
這些方法實現發散/匯聚讀和寫操作. 應用程式偶爾需要做一個包含多個記憶體區的單個讀或寫操作; 這些系統呼叫允許它們這樣做而不必對資料進行額外拷貝. 如果這些函式指標為 NULL, read 和 write 方法被呼叫( 可能多於一次 ).
ssize_t (*sendfile)(struct file *, loff_t *, size_t, read_actor_t, void *);
這個方法實現 sendfile 系統呼叫的讀, 使用最少的拷貝從一個檔案描述符搬移資料到另一個. 例如, 它被一個需要傳送檔案內容到一個網路連線的 web 伺服器使用. 裝置驅動常常使 sendfile 為 NULL.
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
sendpage 是 sendfile 的另一半; 它由核心呼叫來發送資料, 一次一頁, 到對應的檔案. 裝置驅動實際上不實現 sendpage.
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
這個方法的目的是在程序的地址空間找一個合適的位置來對映在底層裝置上的記憶體段中. 這個任務通常由記憶體管理程式碼進行; 這個方法存在為了使驅動能強制特殊裝置可能有的任何的對齊請求. 大部分驅動可以置這個方法為 NULL.[10]
int (*check_flags)(int)
這個方法允許模組檢查傳遞給 fnctl(F_SETFL...) 呼叫的標誌.
int (*dir_notify)(struct file *, unsigned long);
這個方法在應用程式使用 fcntl 來請求目錄改變通知時呼叫. 只對檔案系統有用; 驅動不需要實現 dir_notify.
其中用到了struct file
表示開啟的檔案,具體的點這裡,Struct inode
表示一個磁碟上的具體檔案.
struct file {
/*
* fu_list becomes invalid after file_free is called and queued via
* fu_rcuhead for RCU freeing
*/
union {
struct list_head fu_list;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path;
#define f_dentry f_path.dentry
#define f_vfsmnt f_path.mnt
const struct file_operations *f_op;
atomic_t f_count;
unsigned int f_flags;
mode_t f_mode;
loff_t f_pos;
struct fown_struct f_owner;
unsigned int f_uid, f_gid;
struct file_ra_state f_ra;
unsigned long f_version;
#ifdef CONFIG_SECURITY
void *f_security;
#endif
/* needed for tty driver, and maybe others */
void *private_data;
#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_head f_ep_links;
spinlock_t f_ep_lock;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
};
驅動註冊
實現介面後需要告知核心,也就是註冊介面,註冊到核心中的函式原型如下:
/**
* register_chrdev() - Register a major number for character devices.
* @major: major device number or 0 for dynamic allocation
* @name: name of this range of devices
* @fops: file operations associated with this devices
*
* If @major == 0 this functions will dynamically allocate a major and return
* its number.
*
* If @major > 0 this function will attempt to reserve a device with the given
* major number and will return zero on success.
*
* Returns a -ve errno on failure.
*
* The name of this device has nothing to do with the name of the device in
* /dev. It only helps to keep track of the different owners of devices. If
* your module name has only one type of devices it's ok to use e.g. the name
* of the module here.
*
* This function registers a range of 256 minor numbers. The first minor number
* is 0.
*/
int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
major
為主裝置號,name
為字串標識,fops
就是包含所有介面函式的結構體file_operations
這裡補充下裝置號的知識.在之前的busybox 之 最小根檔案系統
中我們使用mknod
來建立字元裝置或者塊裝置(/dev/console
).
# ls -l /dev/console
crw-rw---- 1 0 0 5, 1 Jan 1 01:05 /dev/console
如何使系統去呼叫這個註冊函式?
這裡使用巨集module_init
來實現,module_init
就是定義一個結構體,這個結構體中有一個函式指標,指向“入口函式”。 當安裝一個驅動程式時,核心會自動找到這樣一個結構體,呼叫裡面的函式指標,入口函式.也就是當我們執行命令去載入驅動的時候,會去遍歷這些指標應該.
先了解自上而下的呼叫過程
app
自主去open("dev/xxx")
,這裡的xxx
裝置檔案為字元裝置,它有標識為字元裝置,以及主裝置號和次裝置號- VFS 系統根據
xxx
的屬性,即字元裝置型別+主裝置號找到註冊到的file_operations
結構,呼叫函式
函式原理
綜上,register_chrdev
函式最簡單的實現方式也就是存在一個chardev
的陣列,按照主裝置號為索引,內容為file_operations
指標即可.所謂註冊也就是填充這個陣列即可
小結
register_chrdev
是實現註冊的方式,module_init
是系統在裝載驅動的時候去尋找到這個註冊的方式.為什麼不直接在register_chrdev
中直接實現module_init
的通知功能.可以這麼理解,裝載驅動的時候可能還需要其他一些動作,系統提供的register_chrdev
只是在這個chardev
陣列增加,並沒有辦法做其他事情,所以這裡一般是以下這種形式
void my_regist_fun()
{
dosomething();
register_chrdev(major,name,&my_file_operations);
}
module_init(my_regist_fun);
驅動解除安裝
同驅動載入註冊一樣,使用unregister_chrdev
實現,使用module_exit
來使得核心能夠主動呼叫
int unregister_chrdev(unsigned int major, const char *name)
同樣的一般實現形式如下
void my_unregist_fun()
{
dosomething();
unregister_chrdev(major,name);
}
module_init(my_regist_fun);
程式設計
(一)手動建立主裝置號
標頭檔案包含
#include <linux/module.h> #include <linux/kernel.h> //核心相關 #include <linux/fs.h> //檔案操作相關結構提 #include <linux/init.h> #include <linux/delay.h> #include <asm/uaccess.h> //核心與使用者資料互動 #include <asm/irq.h> #include <asm/io.h> //ioremap等 io訪問 #include <asm/arch/regs-gpio.h> #include <asm/hardware.h>
Makefile 中需要提前編譯好核心,並且加入其中.
-C
是指使用KERN_DIR
中的Makefile來編譯,M
表示當前目錄,moudle
也就是目標了KERN_DIR = /work/system/linux-2.6.22.6 all: make -C $(KERN_DIR) M=`pwd` modules clean: make -C $(KERN_DIR) M=`pwd` modules clean rm -rf modules.order obj-m += first_drv.o
新增 GPL 的licence,否則在安裝的時候會有警告
MODULE_LICENSE("GPL"); # insmod ./dri.ko dri: module license 'unspecified' taints kernel.
使用
cat /proc/devices
檢視掛載的驅動裝置# cat /proc/devices Character devices: 1 mem ...
程式碼一覽
// 驅動程式 dri.c
#include <linux/module.h>
#include <linux/kernel.h> //核心相關
#include <linux/fs.h> //檔案操作相關結構提
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h> //核心與使用者資料互動
#include <asm/irq.h>
#include <asm/io.h> //ioremap等 io訪問
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
int first_drv_open(struct inode *inode, struct file *file)
{
printk("first_drv_open\n");
return 0;
}
ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
printk("first_drv_write\n");
return 0;
}
struct file_operations first_drv_fops = {
.open = first_drv_open,
.write = first_drv_write,
};
int first_drv_init(void)
{
register_chrdev(111, "first_drv", &first_drv_fops);
return 0;
}
void first_drv_exit(void)
{
unregister_chrdev(111, "first_drv");
}
module_init(first_drv_init);
module_exit(first_drv_exit);
MODULE_LICENSE("GPL");
測試程式test.c
,編譯命令是arm-linux-gcc -o test test.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main(int argc, char **argv)
{
int fd;
int val = 1;
fd = open("xxx", O_RDWR);
if (fd < 0)
{
printf("can't open!\n");
}
write(fd, &val, 4);
return 0;
}
測試
安裝驅動
insmod dri.ko
,如果之前有安裝過,使用rmmod dri.ko
解除安裝驅動檢視驅動是否安裝
cat /proc/devices
,這裡能夠看到主裝置號是111
,裝置名是first_drv
Character devices: ... 111 first_drv ...
建立測試所需的裝置檔案
xxx
,這裡的xxx的主裝置號必須與驅動的一致,否則開啟錯誤,核心是根據主裝置號查詢的,而非名字mknod xyz c 111 5 #111 是主裝置號,5是次裝置號
執行測試程式
./test
,如果沒有步驟3建立裝置檔案,會提示沒有裝置檔案的# ./test first_drv_open first_drv_write
(二)自動分配主裝置號
註冊函式中使用major=0
引數傳遞給註冊函式register_chrdev
時,系統就能自動尋找空閒的一個主裝置號返回.
int major;
int first_drv_init(void)
{
major=register_chrdev(0, "first_drv", &first_drv_fops);
return 0;
}
void first_drv_exit(void)
{
unregister_chrdev(major, "first_drv");
}
這個時候可以手動cat /proc/devices
去檢視裝置號然後去建立裝置去控制,使用cat /proc/devices
檢視裝置號然後去操作
(三)自動建立裝置檔案
在busybox 完善(四)
中提到了mdev
機制,也就是系統自動掛載,mdev會根據sys
下的資訊自動建立裝置的,自動在/proc/sys/kernel/hotplug
完成載入與解除安裝
# cat init.d/rcS
mount -a
mkdir /dev/pts
mount -t devpts devpts /dev/pts
echo /sbin/mdev > /proc/sys/kernel/hotplug #這裡實現熱拔插自動載入資訊
mdev -s
我們這裡的是會在sys
下建立一個class
的資料夾的
程式碼一覽
設計流程
static struct class *firstdrv_class;
static struct class_device *firstdrv_class_dev;
static struct file_operations first_drv_fops = {
.owner = THIS_MODULE, /* 這是一個巨集,推向編譯模組時自動建立的__this_module變數 */
.open=.....
.write=...
}
//建立一個類
firstdrv_class = class_create(THIS_MODULE, "firstdrv");
//在類下面去建立一個裝置 /dev/xyz
firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /*
//解除安裝裝置需要刪除這個類
class_device_unregister(firstdrv_class_dev);
class_destroy(firstdrv_class);
注意 如果沒有加入MODULE_LICENSE("GPL");
,會提示
# insmod dri.ko
dri: Unknown symbol class_device_create
dri: Unknown symbol class_device_unregister
dri: Unknown symbol class_create
dri: Unknown symbol class_destroy
insmod: cannot insert 'dri.ko': Unknown symbol in module (-1): No such file or directory
完整程式碼
#include <linux/module.h>
#include <linux/kernel.h> //核心相關
#include <linux/fs.h> //檔案操作相關結構提
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h> //核心與使用者資料互動
#include <asm/irq.h>
#include <asm/io.h> //ioremap等 io訪問
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
static struct class *firstdrv_class;
static struct class_device *firstdrv_class_dev;
static int first_drv_open(struct inode *inode, struct file *file)
{
printk("first_drv_open\n");
return 0;
}
static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
printk("first_drv_write\n");
return 0;
}
static struct file_operations first_drv_fops = {
.owner = THIS_MODULE, /* 這是一個巨集,推向編譯模組時自動建立的__this_module變數 */
.open = first_drv_open,
.write = first_drv_write,
};
static int major;
static int first_drv_init(void)
{
major=register_chrdev(0, "first_drv", &first_drv_fops); // 註冊, 告訴核心
firstdrv_class = class_create(THIS_MODULE, "first_drv");
firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/xyz */
return 0;
}
static void first_drv_exit(void)
{
unregister_chrdev(major, "first_drv"); // 解除安裝
class_device_unregister(firstdrv_class_dev);
class_destroy(firstdrv_class);
}
module_init(first_drv_init);
module_exit(first_drv_exit);
MODULE_LICENSE("GPL");
測試
安裝
insmod dri.ko
檢視下是否載入,這裡被自動分配了
252
這個主裝置號# cat /proc/devices ... 252 first_drv ... # lsmod Module Size Used by Tainted: P dri 2124 0
執行
./test
# ./test first_drv_open first_drv_write
分析
系統會自動在/sys/class
下建立class
下的檔案資訊first_drv
,可以看到在/sys/class/first_drv/xyz/dev
中的檔案內容就是其主裝置號+次裝置號
# cd /sys/
# ls
block class firmware kernel power
bus devices fs module
# ls class
first_drv mem ppdev scsi_host usb_hostt
# ls /sys/class/first_drv/
xyz
# ls /sys/class/first_drv/xyz
dev subsystem uevent
# cat /sys/class/first_drv/xyz/dev
252:0
(四)使用次裝置號
在(三)自動分配主裝置號中,是在class
結構下掛接具體的class_device
,這裡更改為在同一個class
下掛接多個class_device
.
//定義一個類下面有四個物件,也就是4個子裝置了--------------------------------------
static struct class *firstdrv_class;
static struct class_device *firstdrv_class_dev[4];
//註冊生成次裝置號-------------------------------------------------------------
major=register_chrdev(0, "first_drv", &first_drv_fops); // 註冊, 告訴核心
firstdrv_class = class_create(THIS_MODULE, "first_drv");
for (minor = 0; minor < 4; minor++)
{
firstdrv_class_dev[minor] = class_device_create(firstdrv_class, NULL, MKDEV(major, minor), NULL, "xyz%d", minor);
}
//解除安裝裝置----------------------------------------------------------------------
unregister_chrdev(major, "first_drv"); // 解除安裝
for ( minor = 0; minor < 4; minor++)
{
class_device_unregister(firstdrv_class_dev[minor]);
}
class_destroy(firstdrv_class);
那麼如何區分出次裝置號呢?
//在讀寫函式中,使用file結構指標中的引數
int minor = MINOR(file->f_dentry->d_inode->i_rdev);
//在open函式中,使用node節點
int minor = MINOR(inode->i_rdev);
程式碼一覽
完整的驅動函式程式碼
#include <linux/module.h>
#include <linux/kernel.h> //核心相關
#include <linux/fs.h> //檔案操作相關結構提
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h> //核心與使用者資料互動
#include <asm/irq.h>
#include <asm/io.h> //ioremap等 io訪問
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
static struct class *firstdrv_class;
static struct class_device *firstdrv_class_dev[4];
static int first_drv_open(struct inode *inode, struct file *file)
{
int minor = MINOR(inode->i_rdev);
printk("first_drv_open=%d\n",minor);
return 0;
}
static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
int minor = MINOR(file->f_dentry->d_inode->i_rdev);
printk("first_drv_write=%d\n",minor);
return 0;
}
static struct file_operations first_drv_fops = {
.owner = THIS_MODULE, /* 這是一個巨集,推向編譯模組時自動建立的__this_module變數 */
.open = first_drv_open,
.write = first_drv_write,
};
static int major;
static int first_drv_init(void)
{
int minor=0;
major=register_chrdev(0, "first_drv", &first_drv_fops); // 註冊, 告訴核心
firstdrv_class = class_create(THIS_MODULE, "first_drv");
for (minor = 0; minor < 4; minor++)
{
firstdrv_class_dev[minor] = class_device_create(firstdrv_class, NULL, MKDEV(major, minor), NULL, "xyz%d", minor);
}
return 0;
}
static void first_drv_exit(void)
{
int minor = 0;
unregister_chrdev(major, "first_drv"); // 解除安裝
for ( minor = 0; minor < 4; minor++)
{
class_device_unregister(firstdrv_class_dev[minor]);
}
class_destroy(firstdrv_class);
}
module_init(first_drv_init);
module_exit(first_drv_exit);
MODULE_LICENSE("GPL");
更改下測試程式碼,使用引數傳遞所需開啟的檔案
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main(int argc, char **argv)
{
int fd;
int val = 1;
if (argc <= 1) {printf("nofile select !\n"); return ;}
fd = open(argv[1], O_RDWR);
if (fd < 0)
{
printf("can't open!\n");
}
write(fd, &val, 4);
return 0;
}
測試
首先安裝驅動
insmod dri.ko
查詢驅動
# lsmod Module Size Used by Tainted: P dri 2232 0 # cat /proc/devices Character devices: 252 first_drv
查詢掛載裝置
# ls /dev/xyz* /dev/xyz0 /dev/xyz1 /dev/xyz2 /dev/xyz3
測試
# ./test /dev/xyz0 first_drv_open=0 first_drv_write=0 # ./test /dev/xyz1 first_drv_open=1 first_drv_write=1 # ./test /dev/xyz2 first_drv_open=2 first_drv_write=2
分析
檢視下sys/class
下是否自動建立了xyz
的相關資訊
# ls /sys/class/
first_drv
# ls /sys/class/first_drv/xyz*
/sys/class/first_drv/xyz0:
dev subsystem uevent
/sys/class/first_drv/xyz1:
dev subsystem uevent
/sys/class/first_drv/xyz2:
dev subsystem uevent
/sys/class/first_drv/xyz3:
dev subsystem uevent
# cat /sys/class/first_drv/xyz*/dev
252:0
252:1
252:2
252:3
(五)異常捕獲處理
註冊時的異常捕獲
ret = register_chrdev(LED_MAJOR, DEVICE_NAME, &s3c24xx_leds_fops);
if (ret < 0) {
printk(DEVICE_NAME " can't register major number\n");
return ret;
}
使用類來呼叫mdev
機制建立類
leds_class = class_create(THIS_MODULE, "leds");
if (IS_ERR(leds_class))
return PTR_ERR(leds_class);
類下掛載裝置
leds_class_devs[minor] = class_device_create(leds_class, NULL, MKDEV(LED_MAJOR, minor), NULL, "led%d", minor);
if (unlikely(IS_ERR(leds_class_devs[minor])))
{
return PTR_ERR(leds_class_devs[minor]);
}
(六)驅動資訊
/* 描述驅動程式的一些資訊,不是必須的 */
MODULE_AUTHOR("http://www.100ask.net");
MODULE_VERSION("0.1.0");
MODULE_DESCRIPTION("S3C2410/S3C2440 LED Driver");
MODULE_LICENSE("GPL");
小結
- 應用程式通過
open/write/close
呼叫,open
等通過傳遞的檔案的主裝置號去尋找驅動,執行驅動函式. - 驅動程式中使用
register_chrdev
來註冊驅動,使用class_device
和class
相關函式呼叫mdev
機制達到自動建立裝置檔案的目的 - 主裝置號是提供給系統用的,次裝置號是提供給使用者用的,可以當作一個引數使用
定義
file_operations
結構體,填充開啟,讀寫函式定義註冊函式與解除安裝函式
修飾註冊函式與解除安裝函式
module_init
,module_exit
使用
mdev
需要建立class
類,並在class
類下掛載對應的class_device
裝置節點建立驅動資訊
MODULE_LICENSE("GPL");
等標頭檔案包含
#include <linux/module.h> #include <linux/kernel.h> //核心相關 #include <linux/fs.h> //檔案操作相關結構提 #include <linux/init.h> #include <linux/delay.h> #include <asm/uaccess.h> //核心與使用者資料互動 #include <asm/irq.h> #include <asm/io.h> //ioremap等 io訪問 #include <asm/arch/regs-gpio.h> #include <asm/hardware.h>
Makefile
KERN_DIR = /work/system/linux-2.6.22.6 all: make -C $(KERN_DIR) M=`pwd` modules clean: make -C $(KERN_DIR) M=`pwd` modules clean rm -rf modules.order obj-m += first_drv.o
注意:
視訊順序是LED驅動程式_操作LED
然後是LED驅動程式_測試改進