環境:主機-Ubuntu 16.04,開發板-友善之臂tiny4412開發板,內核版本linux-3.5
參考《Linux設備驅動開發詳解基於最新的Linux 4.0內核》(宋寶華編著)
字符設備驅動,在Linux設備驅動中較爲基礎,本文將大致分析Linux字符設備驅動的整體結構,並編寫簡單的驅動模板。
字符設備:在I/O傳輸過程中以字符爲單位串行順序進行傳輸的設備,即以一個字節一個字節進行讀寫操作的設備,是面向字節流的,如鼠標、鍵盤、串口、GPIO等。
1、字符設備驅動結構
在Linux內核中,使用struct cdev結構體來描述一個字符設備,其定義如下:
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
cdev結構體的dev_t成員定義了設備號,爲32位(高12位爲主設備號,低20位爲次設備號)。設備號相關宏如下:
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) //獲取主設備號
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) //獲取次設備號
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) //指定主、次設備號生成設備號
分別爲獲取主設備號、獲取次設備號、生成設備號。
關於設備號,有兩個重要的函數,分別完成設備號的分配與釋放:
/* 向系統申請設備號,已知起始設備號的情況 */
int register_chrdev_region(dev_t from, unsigned count, const char *name);
/* 向系統動態申請未被佔用的設備號,不需知道設備號 */
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
/* 釋放所申請的設備號 */
void unregister_chrdev_region(dev_t from, unsigned count);
Linux內核提供了一組函數以用於操作cdev結構體:
/* 初始化cdev結構的成員,並建立cdev和file_operation之間的連接 */
void cdev_init(struct cdev *cdev, const struct file_operations *fops);
/* 動態申請一個cdev結構內存 */
struct cdev *cdev_alloc(void);
/* 向系統添加一個字符設備cdev */
int cdev_add(struct cdev *p, dev_t dev, unsigned count);
/* 向系統刪除一個字符設備cdev */
void cdev_del(struct cdev *p);
2、struct file_operations結構體
cdev結構體中的成員file_operation非常重要,其定義了字符設備驅動提供給虛擬文件系統的接口函數,大部分的對設備操作函數都要經過這個結構體。
定義:
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 *);
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 *, loff_t, loff_t, 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 **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
};
3、字符設備驅動編寫步驟
(1)編寫linux內核模塊的加載與卸載函數;
(2)file_operation結構體賦值;
(3)註冊cdev;
(4)實現file_operation中的read()/write()等函數;
筆記:
關於加載/卸載函數的__init、__exit:
在include/init.h中有定義:(在include/linux/complier.h有 # define __section(S) __attribute__ ((__section__(#S))) )
#define __init __section(.init.text) __cold notrace
#define __exit __section(.exit.text) __exitused __cold notrace
“section”關鍵字會將被修飾的變量或函數編譯到指定的區域(可執行文件中的段),如.init.text、.exit.text,詳解請研究__attribute__ ((__section__())) 屬性設置
未完待續。。。