字符設備驅動
1.定義
以字節(jié)流的形式進行訪問,且只能順序訪問的設備,針對字符設備編寫的驅動叫做字符設備驅動
2.字符設備框架
用戶空間通過IO函數(shù)如open、read、write、close等函數(shù)接口,調(diào)用內(nèi)核空間中的字符設備驅動函數(shù)中的用戶自定義的open、read、write、close等函數(shù),通過內(nèi)核驅動函數(shù)去操控相應的字符設備。當設備驅動注冊進內(nèi)核時,內(nèi)核會分配給注冊的設備驅動一個編號,每注冊一個設備驅動都會給一個新的編號,這個編號叫做設備號,設備號是設備驅動在內(nèi)核中的身份標志。設備號是一個32位的數(shù)據(jù),由主設備號和次設備號組成,主設備號用來標識一類設備,次設備號用來標識這一類設備中的其中一個設備,一般一類設備的主設備號是一樣的,但是因為要標識該類設備下的不同設備,因此次設備號是不一致的。設備號中主設備號占據(jù)高12位,次設備號占據(jù)低20位。設備號=主設備號<<20|次設備號。當在注冊驅動后會得到驅動對應的設備號,并且基于這個設備號會在內(nèi)核文件系統(tǒng)中創(chuàng)建一個設備文件,這個設備文件起到了一個承上啟下的作用,在用戶空間中通過IO函數(shù)操作設備文件從而操控設備。
3.字符設備內(nèi)部實現(xiàn)原理
用戶空間調(diào)用IO函數(shù)打開設備文件后,系統(tǒng)為該設備文件分配一個inode號,inode號是文件系統(tǒng)中的唯一標識,同時也是索引當前文件的inode結構體的索引號,只要文件存在于文件系統(tǒng)中,內(nèi)核一定會存在一個inode結構體,這個結構體內(nèi)部存放了文件的相關信息,當驅動設備注冊時,會申請一個設備號,設備號會被填充到inode結構體中。
當用戶空間調(diào)用IO函數(shù)時,根據(jù)open傳遞的文件路徑找到設備文件對應的inode結構體,根據(jù)inode結構體中的設備號從而在inode結構體成員的共用體成員找到字符設備驅動的對象指針,通過該字符設備指針找到該指針指向的字符設備對象結構體,從而找到該結構體中的操作方法結構體指針,根據(jù)操作方法結構體指針回調(diào)對應的文件操作方法,從而控制硬件設備。
設備號是驅動存在在內(nèi)核的標識,是連接設備驅動和設備文件的紐帶,用戶空間調(diào)用IO函數(shù),找到設備文件的inode結構體,再通過設備號,找到相應的驅動對象結構體,通過驅動對象結構體中的操作方法結構體,找到對應的設備文件操作方法函數(shù),回調(diào)該函數(shù),從而控制硬件。
注冊字符設備驅動步驟:
<1>分配字符設備驅動對象空間
<2>初始化字符設備驅動成員--指向字符設備驅動對象的指針和操作方法結構體
<3>申請設備號--為了將設備文件和字符驅動通過設備號連接起來
<4>將字符設備驅動注冊進內(nèi)核文章來源:http://www.zghlxwxcb.cn/news/detail-555347.html
代碼實例:文章來源地址http://www.zghlxwxcb.cn/news/detail-555347.html
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include "led.h"
struct cdev *cdev;
unsigned int major=500;
unsigned int minor=0;
dev_t devno;
struct class *cls;
struct device * dev;
int mycdev_open(struct inode *inode, struct file *file)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
return 0;
}
long mycdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
return 0;
}
int mycdev_close(struct inode *inode, struct file *file)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
return 0;
}
//定義操作方法結構體變量并賦值
struct file_operations fops={
.open=mycdev_open,
.unlocked_ioctl=mycdev_ioctl,
.release=mycdev_close,
};
static int __init mycdev_init(void)
{
int ret,i;
//1.分配對象空間
cdev=cdev_alloc();
if(cdev==NULL)
{
printk("分配字符設備驅動對象失敗\n");
ret=-EFAULT;
goto OUT1;
}
printk("分配對象空間成功\n");
//2.初始化對象
cdev_init(cdev,&fops);
//3.申請設備號
if(major>0)//靜態(tài)指定設備號
{
ret= register_chrdev_region(MKDEV(major,minor),3,"myled");
if(ret)
{
printk("靜態(tài)指定設備號失敗\n");
goto OUT2;
}
}
else if(major==0)//動態(tài)申請設備號
{
ret=alloc_chrdev_region(&devno,minor,3,"myled");
if(ret)
{
printk("動態(tài)申請設備號失敗\n");
goto OUT2;
}
//獲取主設備號和次設備號
major=MAJOR(devno);
minor=MINOR(devno);
}
printk("申請設備號成功\n");
//4.注冊字符設備驅動對象
ret=cdev_add(cdev,MKDEV(major,minor),3);
if(ret)
{
printk("注冊字符設備驅動對象失敗\n");
goto OUT3;
}
printk("注冊字符設備驅動對象成功\n");
//向上提交目錄
cls=class_create(THIS_MODULE,"myled");
if(IS_ERR(cls))
{
printk("向上提交目錄失敗\n");
ret=-PTR_ERR(cls);
goto OUT4;
}
printk("向上提交目錄成功\n");
//向上提交設備節(jié)點信息
for(i=0;i<3;i++)
{
dev=device_create(cls,NULL,MKDEV(major,i),NULL,"myled%d",i);
if(IS_ERR(dev))
{
ret=-PTR_ERR(dev);
goto OUT5;
}
}
printk("向上提交設備節(jié)點成功\n");
//完成硬件寄存器地址的映射以及初始化
return 0;
OUT5:
//釋放已經(jīng)申請的設備節(jié)點信息
for(--i;i>=0;i--)
{
device_destroy(cls,MKDEV(major,i));
}
//釋放目錄空間
class_destroy(cls);
OUT4:
//注銷字符設備驅動對象
cdev_del(cdev);
OUT3:
//釋放設備號
unregister_chrdev_region(MKDEV(major,minor),3);
OUT2:
//釋放對象空間
kfree(cdev);
OUT1:
return ret;
}
static void __exit mycdev_exit(void)
{
//銷毀設備節(jié)點
int i;
for(i=0;i<3;i++)
{
device_destroy(cls,MKDEV(major,i));
}
//釋放目錄空間
class_destroy(cls);
//1.注銷字符設備驅動對象
cdev_del(cdev);
//2.釋放設備號
unregister_chrdev_region(MKDEV(major,minor),3);
//3.釋放對象空間
kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
到了這里,關于驅動開發(fā)--字符驅動設備2的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!