第16章 驅動開發之字元裝置驅動程式框架
阿新 • • 發佈:2019-01-28
16.1 字元裝置驅動程式框架簡介
我們在學習 C 語言的時候,知道每個應用程式的入口函式,即第一個被執行的函式是 main函式,那麼,我們自己的驅動程式,哪個函式是入口函式呢?
在寫驅動程式的時候,如果函式的名字可以任意取,常常為 xxxx_init(),當實現好這個 xxxx_init()函式以後,核心其實並不知道這個就是我們驅動的入口函式,因此我們要想辦法告訴核心,我們的入口函式是哪個?我們通過 module_init()函式來告訴核心,具體如下。
module_init(openwrt_drv_init);
通過上面的修飾以後, openwrt_drv_init()這個函式就變成了我們的驅動程式的入口函數了。當然,有入口函式,自然還需要一個出口函式,我們通過 module_exit()函式來告訴核心,具體如下。
module_exit(openwrt_drv_exit);
從上一章中,我們知道,應用程式是通過 open、read、write …函式來和我們的驅動程式進行互動的,那麼我們的驅動程式是怎麼給應用程式提供這些介面的呢?我們在寫驅動程式的時候,我們首先需要定義出一個 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 *);
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);
};
我們的驅動程式要給應用程式提供哪些介面,就只需要填充相應的成員即可。比如我們想提供 open、read、write 這三個介面,就應該如下定義。
static struct file_operations openwrt_drv_fops = {
.owner = THIS_MODULE, /*這是一個巨集,推向編譯模組時自動建立的__this_module 變數 */
.open = openwrt_drv_open,
.read = openwrt_drv_read,
.write = openwrt_drv_write,
};
當 file_operations 結構體定義、設定好以後,我們只需要通過 register_chrdev()函式將該機構圖註冊進核心即可。
16.2 字元裝置驅動程式框架實現
經過前面部分的講解,相信大家一定對如何寫一個自己的驅動程式,有所感悟了。接下來,給大家一個簡單的驅動程式的例子,可以用於作為框架模板,以後的驅動都可以基於該驅動進行修改。
#include <linux/mm.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/mman.h>
#include <linux/random.h>
#include <linux/init.h>
#include <linux/raw.h>
#include <linux/tty.h>
#include <linux/capability.h>
#include <linux/ptrace.h>
#include <linux/device.h>
#include <linux/highmem.h>
#include <linux/crash_dump.h>
#include <linux/backing-dev.h>
#include <linux/bootmem.h>
#include <linux/splice.h>
#include <linux/pfn.h>
#include <linux/export.h>
#include <linux/io.h>
#include <linux/aio.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <asm/uaccess.h>
/* 載入模式後,執行”cat /proc/devices”命令看到的裝置名稱 */
#define DEVICE_NAME "openwrt"
#define OPENWRT_MAJOR 0 /* 主裝置號 */
static struct class *openwrt_drv_class;
static int openwrt_drv_open(struct inode *inode, struct file *file)
{
// printk 用於驅動中新增列印,用法和應用程式中的 printf 一樣
printk("%s:Hello openwrt\n", __FUNCTION__);
return 0;
}
static ssize_t openwrt_drv_read(struct file *file, char __user *buf, size_t size,loff_t *ppos)
{
// printk 用於驅動中新增列印,用法和應用程式中的 printf 一樣
printk("%s:Hello openwrt\n", __FUNCTION__);
return 0;
}
static ssize_t openwrt_drv_write(struct file *file, const char __user *buf, size_t
size, loff_t *ppos)
{
// printk 用於驅動中新增列印,用法和應用程式中的 printf 一樣
printk("%s:Hello openwrt\n", __FUNCTION__);
return 0;
}
/*
*這個結構是字元裝置驅動程式的核心
*當應用程式操作裝置檔案時所呼叫的 open、read、write 等函式,
*最終會呼叫這個結構中指定的對應函式
*/
static struct file_operations f403tech_drv_fops = {
/*這是一個巨集,推向編譯模組時自動建立的__this_module 變數 */
.owner = THIS_MODULE,
.open = openwrt_drv_open,
.read = openwrt_drv_read,
.write = openwrt_drv_write,
};
int major;
/*
*執行 insmod 命令時就會呼叫這個函式
*/
static int __init f403tech_drv_init(void)
{
/* 註冊字元裝置
*這步是寫字元裝置驅動程式所必須的
*引數為主裝置號、裝置名字、file_operations 結構;
*這樣,主裝置號就和具體的 file_operations 結構聯絡起來了,
*操作主裝置為 OPENWRT_MAJOR 的裝置檔案時,就會呼叫 openwrt_drv_fops 中的相關成員函式
*OPENWRT_MAJOR 可以設為 0,表示由核心自動分配主裝置號
*/
major = register_chrdev(OPENWRT_MAJOR, DEVICE_NAME, &openwrt_drv_fops);
if (major < 0)
{
printk(DEVICE_NAME " can't register major number\n");
return major;
}
/*
*以下兩行程式碼用於建立裝置節點,是必須的。
*當然,如果不寫這兩行,那麼就必須在 linux 系統命令列中通過 mknod 這個命令來建立裝置節點
*/
/* 建立類 */
openwrt_drv_class = class_create(THIS_MODULE, "openwrt");
/* 類下面建立裝置節點 */
device_create(openwrt_drv_class, NULL, MKDEV(major, 0), NULL, "openwrt");
// /dev/openwrt
//列印一個除錯資訊
printk("%s:Hello openwrt\n", __FUNCTION__);
return 0;
}
/*
*執行 rmmod 命令時就會呼叫這個函式
*/
static void __exit openwrt_drv_exit(void)
{
// 與入口函式的 register_chrdev函式配對使用
unregister_chrdev(major, "openwrt");
//與入口函式的device_create 函式配對使用
device_destroy(openwrt_drv_class, MKDEV(major, 0));
class_destroy(openwrt_drv_class); // 與入口函式的 class_create 函式配對使用
printk("%s:Hello openwrt\n", __FUNCTION__); // printk 用於驅動中新增列印
}
/* 這兩行指定驅動程式的初始化函式和解除安裝函式 */
module_init(openwrt_drv_init);
module_exit(openwrt_drv_exit);
/* 描述驅動程式的一些資訊,不是必須的 */
MODULE_AUTHOR("openwrt");
MODULE_VERSION("0.1.0");
MODULE_DESCRIPTION("RT5350 FIRST Driver");
MODULE_LICENSE("GPL");