例程代碼路徑:ELF 1開發(fā)板資料包3-例程源碼3-2 驅(qū)動例程源碼2_字符驅(qū)動mydevice
mydevice.c源碼
(一)首先包含頭文件
#include <linux/module.h> ??????// 包含模塊相關(guān)函數(shù)的頭文件
#include <linux/fs.h> ??????????// 包含文件系統(tǒng)相關(guān)函數(shù)的頭文件 #include <linux/uaccess.h> ?????// 包含用戶空間數(shù)據(jù)訪問函數(shù)的頭文件 #include <linux/cdev.h> ????????//包含字符設(shè)備頭文件 |
(二)聲明變量
#define DEVICE_NAME "mydevice" ?// 設(shè)備名稱
static dev_t dev_num; ???????????//分配的設(shè)備號 struct ?cdev my_cdev; ?????????//字符設(shè)備指針 int major; ?//主設(shè)備號 int minor; ?//次設(shè)備號 |
(三)驅(qū)動模塊入口和出口函數(shù)
module_init(mydevice_init); // 指定驅(qū)動程序的初始化函數(shù)
module_exit(mydevice_exit); // 指定驅(qū)動程序的清理函數(shù) |
(四)入口函數(shù)和出口函數(shù)實(shí)現(xiàn)
static int __init mydevice_init(void)
{ int ret; // 在這里執(zhí)行驅(qū)動程序的初始化操作 // 注冊字符設(shè)備驅(qū)動程序 ret = alloc_chrdev_region(&dev_num,0,1,DEVICE_NAME); if (ret < 0) { printk(KERN_ALERT "Failed to allocate device number: %dn", ret); return ret; } major=MAJOR(dev_num); ??????//獲取主設(shè)備號 minor=MINOR(dev_num); ??????//獲取次設(shè)備號 printk(KERN_INFO "major number: %dn",major); printk(KERN_INFO "minor number: %dn",minor); my_cdev.owner = THIS_MODULE; cdev_init(&my_cdev,&fops); ???//初始化字符設(shè)備結(jié)構(gòu)體 cdev_add(&my_cdev,dev_num,1); ?//將字符設(shè)備添加到內(nèi)核中 printk(KERN_INFO "Device registered successfully.n"); return 0; } static void __exit mydevice_exit(void) { // 在這里執(zhí)行驅(qū)動程序的清理操作 // 刪除字符設(shè)備 cdev_del(&my_cdev); // 注銷字符設(shè)備驅(qū)動程序 unregister_chrdev(0, DEVICE_NAME); printk(KERN_INFO "Device unregistered.n"); } |
入口函數(shù)mydevice_init(void)中主要有以下幾個函數(shù)的調(diào)用:
(1)調(diào)用int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, const char *name);函數(shù)進(jìn)行設(shè)備號分配。
dev:指向dev_t類型的指針,用于接收分配的設(shè)備號范圍的起始設(shè)備號;
firstminor:指定次設(shè)備號的起始值。次設(shè)備號用于區(qū)分同一主設(shè)備號下的不同設(shè)備實(shí)例;
count:指定要分配的設(shè)備號數(shù)量;
name:指定字符設(shè)備的名稱,這是一個用于標(biāo)識設(shè)備的字符串,可以在/proc/devices中找到;
(2)調(diào)用MAJOR(dev)獲取主設(shè)備號:
MAJOR(dev)宏接受一個dev_t類型的設(shè)備號作為參數(shù),并返回該設(shè)備號的主設(shè)備號。
(3)調(diào)用MINOR(dev)獲取次設(shè)備號:
MINOR(dev)宏接受一個dev_t類型的設(shè)備號作為參數(shù),并返回該設(shè)備號的次設(shè)備號。
(4)調(diào)用void cdev_init(struct cdev *cdev, const struct file_operations *fops);函數(shù)初始化字符設(shè)備結(jié)構(gòu)體。
cdev:指向要初始化的struct cdev結(jié)構(gòu)體的指針;
fops:指向包含設(shè)備操作函數(shù)的struct file_operations結(jié)構(gòu)體的指針;
(5)調(diào)用int cdev_add(struct cdev *p, dev_t dev, unsigned int count);函數(shù)將字符設(shè)備添加到內(nèi)核中。
p:指向要添加的struct cdev結(jié)構(gòu)體的指針;
dev:指定要添加的設(shè)備號。通常是由alloc_chrdev_region或register_chrdev_region函數(shù)分配得到的設(shè)備號;
count:指定要添加的設(shè)備號數(shù)量;
cdev_add函數(shù)將字符設(shè)備添加到內(nèi)核中,并與指定的設(shè)備號相關(guān)聯(lián)。當(dāng)成功添加字符設(shè)備后,它將被分配一個主設(shè)備號和次設(shè)備號,并成為系統(tǒng)中的一個可用設(shè)備;
注意:在調(diào)用cdev_add之前,應(yīng)先使用cdev_init函數(shù)對struct cdev進(jìn)行初始化,并設(shè)置正確的文件操作函數(shù)。
出口函數(shù)mydevice_exit(void)中主要有以下兩個函數(shù)的調(diào)用:
(1)調(diào)用void unregister_chrdev(unsigned int major, const char *name);函數(shù)注銷已注冊的字符設(shè)備。
major:要注銷的字符設(shè)備的主設(shè)備號;
name:字符設(shè)備的名稱;
unregister_chrdev函數(shù)將指定的字符設(shè)備從內(nèi)核中注銷,并釋放相關(guān)的資源。注銷后,該字符設(shè)備將不再可用;
(2)調(diào)用void cdev_del(struct cdev *p);函數(shù)刪除字符設(shè)備。
p:指向要刪除的struct cdev結(jié)構(gòu)體的指針;
cdev_del函數(shù)從內(nèi)核中刪除指定的字符設(shè)備,并釋放相關(guān)的資源。刪除后,該字符設(shè)備將不再可用;
(五)定義fops結(jié)構(gòu)體
static struct file_operations fops = {
.owner = THIS_MODULE, .open = device_open, .release = device_release, .read = device_read, .write = device_write, }; |
file_operations結(jié)構(gòu)體定義如下:
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 *); int (*open) (struct inode *, struct file *); int (*release) (struct inode *, struct file *); // 其他成員函數(shù)... }; |
file_oprations結(jié)構(gòu)中定義了非常多的成員,但是對于大多數(shù)字符驅(qū)動而言,只需要實(shí)現(xiàn),其中很少的幾個方法即可。下面將忽略不常用的成員,對常用成員進(jìn)行介紹:
(1)owner:指向持有該結(jié)構(gòu)體的模塊的指針;
(2)llseek:用于實(shí)現(xiàn)文件的定位(seek)操作;
(3)read:用于實(shí)現(xiàn)從文件讀取數(shù)據(jù)的操作;
(4)write:用于實(shí)現(xiàn)向文件寫入數(shù)據(jù)的操作;
(5)open:用于打開文件時的處理操作;
(6)release:用于關(guān)閉文件時的處理操作;
(六)設(shè)備操作函數(shù)的實(shí)現(xiàn)
static int device_open(struct inode *inode, struct file *file)
{ // 在這里處理設(shè)備打開的操作 printk(KERN_INFO "This is device_open.n"); return 0; } static int device_release(struct inode *inode, struct file *file) { // 在這里處理設(shè)備關(guān)閉的操作 printk(KERN_INFO "This is device_release.n"); return 0; } static ssize_t device_read(struct file *file, char __user *buffer, size_t length, loff_t *offset) { // 在這里處理設(shè)備讀取的操作 printk(KERN_INFO "This is device_read.n"); return 0; } static ssize_t device_write(struct file *file, const char __user *buffer, size_t length, loff_t *offset) { // 在這里處理設(shè)備寫入的操作 printk(KERN_INFO "This is device_write.n"); return 0; } |
(七)聲明模塊信息
MODULE_LICENSE("GPL"); // 指定模塊的許可證信息
MODULE_AUTHOR("Your Name"); // 指定模塊的作者信息 MODULE_DESCRIPTION("A simple Hello World driver"); // 指定模塊的描述信息 |
完整mydevice.c示例源碼
#include <linux/module.h> ??????// 包含模塊相關(guān)函數(shù)的頭文件
#include <linux/fs.h> ??????????// 包含文件系統(tǒng)相關(guān)函數(shù)的頭文件 #include <linux/uaccess.h> ?????// 包含用戶空間數(shù)據(jù)訪問函數(shù)的頭文件 #include <linux/cdev.h> ????????//包含字符設(shè)備頭文件 #define DEVICE_NAME "mydevice" ?// 設(shè)備名稱 static dev_t dev_num; ??//分配的設(shè)備號 struct ?cdev my_cdev; ?????????//字符設(shè)備指針 int major; ?//主設(shè)備號 int minor; ?//次設(shè)備號 static int device_open(struct inode *inode, struct file *file) { // 在這里處理設(shè)備打開的操作 printk(KERN_INFO "This is device_open.n"); return 0; } static int device_release(struct inode *inode, struct file *file) { // 在這里處理設(shè)備關(guān)閉的操作 printk(KERN_INFO "This is device_release.n"); return 0; } static ssize_t device_read(struct file *file, char __user *buffer, size_t length, loff_t *offset) { // 在這里處理設(shè)備讀取的操作 printk(KERN_INFO "This is device_read.n"); return 0; } static ssize_t device_write(struct file *file, const char __user *buffer, size_t length, loff_t *offset) { // 在這里處理設(shè)備寫入的操作 printk(KERN_INFO "This is device_write.n"); return 0; } static struct file_operations fops = { .owner = THIS_MODULE, .open = device_open, .release = device_release, .read = device_read, .write = device_write, }; static int __init mydevice_init(void) { int ret; // 在這里執(zhí)行驅(qū)動程序的初始化操作 // 注冊字符設(shè)備驅(qū)動程序 ret = alloc_chrdev_region(&dev_num,0,1,DEVICE_NAME); if (ret < 0) { printk(KERN_ALERT "Failed to allocate device number: %dn", ret); return ret; } major=MAJOR(dev_num); ??//獲取主設(shè)備號 minor=MINOR(dev_num); ??//獲取次設(shè)備號 printk(KERN_INFO "major number: %dn",major); printk(KERN_INFO "minor number: %dn",minor); my_cdev.owner = THIS_MODULE; cdev_init(&my_cdev,&fops); ??//初始化字符設(shè)備結(jié)構(gòu)體 cdev_add(&my_cdev,dev_num,1); ??//將字符設(shè)備添加到內(nèi)核中 printk(KERN_INFO "Device registered successfully.n"); return 0; } static void __exit mydevice_exit(void) { // 在這里執(zhí)行驅(qū)動程序的清理操作 // 刪除字符設(shè)備 cdev_del(&my_cdev); // 注銷字符設(shè)備驅(qū)動程序 unregister_chrdev(0, DEVICE_NAME); printk(KERN_INFO "Device unregistered.n"); } module_init(mydevice_init); module_exit(mydevice_exit); MODULE_LICENSE("GPL"); ?????// 指定模塊的許可證信息 MODULE_AUTHOR("Your Name"); // 指定模塊的作者信息 MODULE_DESCRIPTION("A simple character device driver"); // 指定模塊的描述信息 |
編譯
復(fù)制helloworld驅(qū)動中的Makefile文件,將其中的hello.o修改為mydevice.o,效果如下:
設(shè)置環(huán)境變量,編譯:
?. /opt/fsl-imx-x11/4.1.15-2.0.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi
elf@ubuntu:~/work/test/02_字符驅(qū)動/mydevice$?make |
將生成的mydevice.ko文件拷貝到開發(fā)板中。
編寫測試應(yīng)用源碼device_app.c
驅(qū)動中已經(jīng)給應(yīng)用層提供了open、read、write的接口,接下來應(yīng)用層就可以進(jìn)行相應(yīng)的系統(tǒng)調(diào)用。
#include <stdio.h>
#include <stdlib.h> #include <unistd.h> #include <sys/ioctl.h> #include <errno.h> #include <fcntl.h> #define DEV_NAME "/dev/mydevice" int main(int argc, char *argv[]) { int reg; int fd = 0; int dat = 0; fd = open (DEV_NAME, O_RDWR); if (fd < 0) { perror("Open "DEV_NAME" Failed!n"); exit(1); } reg = read(fd, &dat, 1); if (reg<0) { perror("read "DEV_NAME" Failed!n"); exit(1); } dat = 0; reg = write(fd, &dat, 1); if (reg<0) { perror("write "DEV_NAME" Failed!n"); exit(1); } close(fd); return 0; } |
設(shè)置環(huán)境變量:
?. /opt/fsl-imx-x11/4.1.15-2.0.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi |
編譯,將生成的device_app文件拷貝到開發(fā)板中:
elf@ubuntu:~/work/test/02_字符驅(qū)動/device_app$ $CC device_app.c -o device_app |
測試
(一)加載驅(qū)動
root@ELF1:~# insmod mydevice.ko
major number: 246 minor number: 0 Device registered successfully. |
(二)可以看到生成的主設(shè)備號為246,次設(shè)備號為0。我們需要使用主設(shè)備號和次設(shè)備號手動創(chuàng)建/dev/下的設(shè)備節(jié)點(diǎn)。
root@ELF1:~# mknod /dev/mydevice c 246 0 |
mknod的基本語法如下:
mknod <device_file> <file_type> <major> <minor> |
<device_file>:指定要創(chuàng)建的設(shè)備文件節(jié)點(diǎn)的路徑和名稱;
<file_type>:指定設(shè)備文件的類型,可以是b(塊設(shè)備)或c(字符設(shè)備);
<major>:指定設(shè)備文件的主設(shè)備號;
<minor>:指定設(shè)備文件的次設(shè)備號;
(三)運(yùn)行測試應(yīng)用程序
root@ELF1:~# ./device_app
This is device_open. This is device_read. This is device_write. This is device_release. |
(四)卸載驅(qū)動
root@ELF1:~# rmmod mydevice.ko
Device unregistered. |