一、源代碼
在驅動模塊闡述的基礎上增加了對char設備的支持,雖然還不完善,但我們步步爲營,最終做出一個有實用價值的驅動來:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#define NAME "linux_rookie"
int major;
static int chardev_open(struct inode *inode, struct file *file)
{
/*在此文中我們先不操作硬件用打印函數先來驗證結構的有效性*/
printk(KERN_INFO "chardev_open ...\n");
return 0;
}
static int chardev_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "chardev_release ...\n");
return 0;
}
/*這裏是char設備驅動的重頭戲file_operations 是一個龐大的結構,匯聚內核中文件的操作,目前測試我們僅僅是測試open和release功能*/
static const struct file_operations char_fops = {
.owner = THIS_MODULE, // 代表當前模塊
.open = chardev_open, // 應用程序open打開這個設備時實際調用
.release = chardev_release, // 與.open對應的函數
};
// 模塊加載
static int __init chardev_init(void)
{
printk(KERN_INFO "chardev_init ...\n");
major = register_chrdev(0, NAME, &char_fops);
if (major < 0)
{
printk(KERN_ERR "register_chrdev error ...\n");
return -EINVAL;
}
printk(KERN_INFO "register_chrdev OK ... major = %d.\n", major);
return 0;
}
// 模塊卸載
static void __exit chardev_exit(void)
{
printk(KERN_INFO "chrdev_exit ...\n");
unregister_chrdev(major, NAME);
}
module_init(chardev_init);
module_exit(chardev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("linux_rookie"); // AUTHOR作者顧名思義填寫作者
MODULE_DESCRIPTION("linux 字符設備驅動(一)"); // 添加模塊描述信息
二、解析
這是一段非常簡陋的程序,幾年前剛開始學習驅動時也費了一番功夫去理解。驅動開發的邏輯跟應用開發雖然類似,但是突如其來的內核框架着實讓人唏噓了一把。現在有空將這些經歷整理整理希望能對大家有些幫助。
1、file_operations 這是我們要翻越的第一個小山丘:
在內核Fs.h (include\linux)中我們可以找到file_operations:
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 *, 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 (*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 (*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);
int (*setlease)(struct file *, long, struct file_lock **);
};
我們可以悉數從中看到諸如:read、write、open、ioctl等在應用開發中的二級函數(man手冊 man 2 一般指系統調用函數)相仿的身影,內部包含驅動內核模塊提供的對設備進行各種操作的函數的指針。該結構體的每個域都對應着驅動內核模塊用來處理某個被請求的事務的函數的地址。
2、major = register_chrdev(0, NAME, &char_fops);
unregister_chrdev(major, NAME);
再來分析一下另外多出的兩個函數register_chrdev()向內核註冊字符設備驅動我們分別來說一說參數和返回值:
函數原型static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
1)major:傳入的主設備號,對於驅動來說一個設備都有自己的主設備號,且不能重複,如果手工添加需要用cat /proc/device 看一看空缺的主設備號再設置值例如:
[root@board mnt]# cat /proc/devices
Character devices:
1 mem
2 pty
3 ttyp
4 /dev/vc/0
4 tty
5 /dev/tty
5 /dev/console
5 /dev/ptmx
7 vcs
10 misc
13 input
14 sound
21 sg
29 fb
81 video4linux2
89 i2c
90 mtd
108 ppp
116 alsa
128 ptm
136 pts
180 usb
189 usb_device
204 s3c6410_serial
251 hidraw
252 s3c_bc
253 pvrsrvkm
254 rtc
最左側的阿拉伯數字就是主設備號,空缺的可以使用。
還可以傳入參數“0“讓內核自動分配(我們傳入0看看結果):
[root@board mnt]# insmod char2.ko
[10875.791061] chardev_init ...
[10875.792885] register_chrdev OK ... major = 250.
果真給我們自動分配了一個主設備號250”二百五“呵呵。
2)name 設備名稱
這裏我們的命名爲linux_rookie,同樣可以在 /proc/device 下看到
......
250 linux_rookie
......
3)fops 傳入自定義結構體指針file_operations 把我們剛纔自定義的chardev_open()和chardev_release()通過char_fops 告訴內核。這樣我們在應用層調用open時就會調用chardev_open() 。
3、最後我們說一說MODULE_*吧
簡單概述爲一句話:MODULE_xxx是一類宏用來添加模塊描述信息。
可以通過modinfo 查看編譯好的*.ko 文件
bupt@machine:~/work/project/driver/char/char2$ modinfo char2.ko
filename: /home/bupt/work/project/driver/char/char2/char2.ko
description: linux 字符設備驅動(一)
author: linux_rookie
license: GPL
depends:
vermagic: 2.6.35.7 preempt mod_unload ARMv7
這樣是不是就一一對應上了。
總結:
1、module_init ()和 module_exit() 對應的是模塊的註冊與註銷可以通過 lsmod命令查看模塊的信息。
2、register_chrdev() 和unregister_chrdev() 對應的是設備的註冊與註銷,可通過 /proc/device 來觀察分析。
自定義char設備驅動函數可以通過file_operations 作爲橋樑賦值後通過register_chrdev() 告訴內核。