姓名:鄭煜爍? 學(xué)號:19029100010? 學(xué)院:電子工程學(xué)院
轉(zhuǎn)自:https://blog.csdn.net/u012142460/article/details/78906576
【嵌牛導(dǎo)讀】簡單介紹字符設(shè)備以及字符設(shè)備驅(qū)動等
【嵌牛鼻子】字符設(shè)備驅(qū)動框架
【嵌牛提問】何為字符設(shè)備驅(qū)動
【嵌牛正文】
前面我們講了linux驅(qū)動框架linux驅(qū)動(一)驅(qū)動框架虎锚,對驅(qū)動的基本框架有了了解∈υ妫現(xiàn)在我們來說一說字符設(shè)備驅(qū)動,我們一般講驅(qū)動分為三類芒填,字符設(shè)備、塊設(shè)備栓撞、網(wǎng)絡(luò)設(shè)備絮蒿。字符設(shè)備和塊設(shè)備是按照傳輸時的基本單位來劃分的,字符設(shè)備就是傳輸時是按字符來傳輸?shù)耐迓热绱凇PIO砌梆、SPI等卵蛉。字符設(shè)備如硬盤等按照塊傳輸?shù)脑O(shè)備,塊設(shè)備和網(wǎng)絡(luò)設(shè)備的驅(qū)動我們跟多是做移植的工作么库,字符設(shè)備種類繁多且不算復(fù)雜傻丝,所以就會自己來寫。
? ? ? ? 一 設(shè)備號
? ? ? ? 這么多設(shè)備如何區(qū)分诉儒,這就是設(shè)備號的作用葡缰,設(shè)備號又分為主設(shè)備號和次設(shè)備號。主設(shè)備號表征設(shè)備屬于哪一類設(shè)備忱反,比如串口設(shè)備泛释。次設(shè)備號表示主設(shè)備號下的具體哪個設(shè)備。比如說串口1温算、串口2怜校、串口3等等。
? 用4字節(jié)來表示設(shè)備號注竿,其中主設(shè)備號占用 高 12位茄茁,次設(shè)備號占用 低 20位。
? ? ? ? (1) linux提供了一組宏來生成設(shè)備號
? ? ? ? ? #define MINORBITS20
? #define MINORMASK((1U << MINORBITS) - 1)
? #define MAJOR(dev)((unsigned int) ((dev) >> MINORBITS))? ? 獲取主設(shè)備號
? #define MINOR(dev)((unsigned int) ((dev) & MINORMASK))? ? 獲取次設(shè)備號
? #define MKDEV(ma,mi)(((ma) << MINORBITS) | (mi))? // ma << 20 | mi? 生成設(shè)備號
用起來還是很簡單的巩割。
int major = 250;
int minor = 0;
int devno;
devno = MKDEV(major, minor);
? ? ? ? (2) 申請注冊設(shè)備號
? ? ? ? 上面生成設(shè)備號之后裙顽,我們要將這個設(shè)備號注冊到內(nèi)核當(dāng)中,確保沒有注冊過即可宣谈。這是靜態(tài)注冊愈犹,還有動態(tài)注冊。
? ? ? ? ? int register_chrdev_region(dev_t from, unsigned count, const char *name)
參數(shù):from? 設(shè)備號
count? 次設(shè)備的數(shù)量
name? 設(shè)備號的名稱
返回值:成功 0闻丑, 出錯負(fù)數(shù)的錯誤碼
? ? ? 這個注冊函數(shù)可以注冊多個設(shè)備漩怎,在count處設(shè)置即可勋颖。設(shè)備號的名稱是讓內(nèi)核用的,一會兒我們說到創(chuàng)建設(shè)備文件名稱是讓用戶看的勋锤,這兩者不要混淆牙言。
? ? ? 有注冊就會有取消注冊
? ? ? ? ? void unregister_chrdev_region(dev_t from, unsigned count)
? 參數(shù):from? 設(shè)備號
? ? ? ? count? 次設(shè)備的數(shù)量
? ? ? ? ? 設(shè)備注冊之后那里可以查看呢?看這里 proc/devcies文件
這里分了字符設(shè)備和塊設(shè)備怪得。前面表示的就是設(shè)備號
二 字符設(shè)備對象
? ? ? C語言是面向過程的語言,其他高級語言如c++卑硫,java等都是面向?qū)ο蟮耐搅担嫦驅(qū)ο蟮暮锰幉谎远鳎@里不說了欢伏。c語言中也可以利用結(jié)構(gòu)體來實現(xiàn)一個面向?qū)ο蟮倪^程入挣。上面我們向內(nèi)核注冊了一個設(shè)備號,那這個設(shè)備號用來干嘛呢硝拧?我們對應(yīng)這個設(shè)備號就要生成并向內(nèi)核注冊一個字符設(shè)備對象径筏,來表明這個設(shè)備實現(xiàn)的功能。
? ? ? ? 一個字符設(shè)備對象如下
? struct cdev {
struct kobject kobj;? // 設(shè)備對象的基類
struct module *owner;? // 直接賦值為THIS_MODULE? 模塊的擁有者
const struct file_operations *ops; // 文件操作集合
struct list_head list;? // 包含此結(jié)構(gòu)體的成員障陶,都是內(nèi)核循環(huán)雙鏈表節(jié)點
dev_t dev;? // 設(shè)備號
unsigned int count;? // 次設(shè)備的數(shù)量
};
? ? ? ? 我們主要關(guān)注ops和dev就行滋恬,dev就是我們的設(shè)備號,每一個設(shè)備號對應(yīng)一個設(shè)備對象抱究。ops中就是一堆的操作方法恢氯。對象中必須得有方法,設(shè)備對象的方法在哪里鼓寺?全部都在ops當(dāng)中勋拟。我們來看看ops到底都有啥
struct file_operations {
struct module *owner;? ? //直接賦值為THIS_MODULE? 模塊的擁有者
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);
};
? ? ? ? ops中有好多操作方法,我們不用全部實現(xiàn)妈候。我們重點關(guān)注標(biāo)紅的那幾個即可敢靡,我們想想在應(yīng)用層操作一個文件時常用的有那幾個呢?open close read write苦银,就是對應(yīng)我們這里的open release read write啸胧,操作設(shè)備時我們還經(jīng)常會用ioctl,比如說串口設(shè)置波特率幔虏,對應(yīng)我們這里的unlocked_ioctl吓揪。
? ? ? ? 該實現(xiàn)的方法都實現(xiàn)后,我們將實現(xiàn)的方法和對象綁定到一起所计,其實就是設(shè)備對象的方法集合初始化柠辞。
? void cdev_init(struct cdev *cdev, const struct file_operations *fops)
? 參數(shù):cdev? 字符設(shè)備對象
? ? ? ? ? ? fops? 文件操作集合
? ? ? 使用時候的框架如下
dev_t? devno;
int major = 250;
int minor = 0;
int count = 1;
struct cdev cdev;? //設(shè)備對象
int? demo_open(struct inode *inodep, struct file * filep) // 打開設(shè)備
{
? ? return 0;
}
int demo_release(struct inode * inodep, struct file * filep) // 關(guān)閉設(shè)備
{
? ? return 0;
}
ssize_t demo_read(struct file * filep, char __user * buffer, size_t size, loff_t * offlen)
{
? ? return size;
}
ssize_t demo_write(struct file *filep, const char __user *buffer, size_t size, loff_t * offlen)
{
return size;
}
long demo_ioctl(struct file * filep, unsigned int cmd, unsigned long arg)
{
? return 0;
}
struct file_operations fops = {
.owner = THIS_MODULE,
.open = demo_open,
.release = demo_release,
.read = demo_read,
.write = demo_write,
.unlocked_ioctl = demo_ioctl,
};
好,設(shè)備對象的初始化也完成了主胧,我們還得把這個設(shè)備對象告訴內(nèi)核叭首,內(nèi)核才知道有這個東東习勤。這就是設(shè)備對象的注冊,或者叫做設(shè)備對象的添加焙格。? ?
? int cdev_add(struct cdev *p, dev_t dev, unsigned count)
? 參數(shù):p? 字符設(shè)備對象
? ? ? ? dev 設(shè)備號
? ? ? ? count? 次設(shè)備的數(shù)量
? 返回值:成功 0图毕,出錯 負(fù)數(shù)的錯誤碼
有注冊就會有取消注冊
? void cdev_del(struct cdev *p)
這里稍微注意注冊設(shè)備號和注冊設(shè)備對象的順序,先注冊設(shè)備號眷唉,再注冊設(shè)備對象予颤。很好理解嘛,注冊設(shè)備對象時都得有設(shè)備號的參數(shù)了冬阳,內(nèi)核要把設(shè)備對象綁定到設(shè)備號上了蛤虐,你不得先向內(nèi)核注冊設(shè)備號嗎?
取消注冊的時候順序反過來肝陪,先取消注冊設(shè)備對象驳庭,再取消注冊設(shè)備號。
? ? ? 上面講的都是內(nèi)核層面氯窍,我們在應(yīng)用層使用open時的第一個參數(shù)是啥饲常,是要打開文件的路徑,詳細(xì)情況參考linux學(xué)習(xí)(十六):文件IO狼讨。那怎么和我們之前說的一大堆對應(yīng)上呢贝淤?我們知道linux中,一切皆文件政供。驅(qū)動設(shè)備也一樣霹娄。驅(qū)動設(shè)備文件在哪里,我們查看/dev/,那里就是設(shè)備文件統(tǒng)一聚集地鲫骗。這里的文件就是和應(yīng)用層交互用的犬耻。
我們怎么創(chuàng)建一個設(shè)備文件呢?我們來看看手動方式执泰,執(zhí)行shell命令
mknod name c 主設(shè)備號 次設(shè)備號
name表示設(shè)備文件名稱枕磁,注意和之前說的注冊設(shè)備號名稱區(qū)別,那個是給內(nèi)核用的术吝,這兩者名稱不必相同计济。
c 表示設(shè)備類型為字符設(shè)備
后面為 主設(shè)備號和次設(shè)備號
執(zhí)行這條指令之后,linux變會將創(chuàng)建出來的設(shè)備文件和之前注冊的設(shè)備號和設(shè)備對象綁定在一起排苍。具體的linux如何從應(yīng)用層到底層沦寂,我們下一篇再來詳細(xì)描述一下。
總結(jié)一下上面字符設(shè)備驅(qū)動的一個基本過程:
1淘衙、生成設(shè)備號
2传藏、向內(nèi)核注冊該設(shè)備號
3、初始化設(shè)備對象,完成操作方法集
4毯侦、向內(nèi)核注冊該設(shè)備對象
5哭靖、生成設(shè)備文件,供用戶層調(diào)用侈离。
百聞不如一見试幽,看一個簡單的例子
驅(qū)動層:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/fs.h>
MODULE_LICENSE("GPL");
dev_t? devno;
int major = 250;
int minor = 0;
int count = 1;
struct cdev cdev;
int? demo_open(struct inode *inodep, struct file * filep) // 打開設(shè)備
{
printk("%s,%d\n", __func__, __LINE__);
return 0;
}
int demo_release(struct inode * inodep, struct file * filep)? // 關(guān)閉設(shè)備
{
printk("%s,%d\n", __func__, __LINE__);
return 0;
}
struct file_operations? fops = {
.owner = THIS_MODULE,
.open = demo_open,
.release = demo_release,
};
static int __init demo_init(void)
{
int ret = 0;
printk("%s,%d\n", __func__, __LINE__);
devno = MKDEV(major, minor);
printk("devno:%d\n", devno);
ret = register_chrdev_region(devno, count, "xxx");
if(ret)
{
printk("Failed to register_chrdev_region.\n");
return ret;
}
cdev_init(&cdev, &fops);
cdev.owner = THIS_MODULE;
ret = cdev_add(&cdev, devno, count);
if(ret)
{
printk("Failed to cdev_add.\n");
unregister_chrdev_region(devno, count);
return ret;
}
return 0;
}
static void __exit demo_exit(void)
{
printk("%s,%d\n", __func__, __LINE__);
cdev_del(&cdev);
unregister_chrdev_region(devno, count);
}
module_init(demo_init);
module_exit(demo_exit);
這里只實現(xiàn)了簡單的open close,框架嘛對吧 其余的根據(jù)功能實現(xiàn)就是了卦碾。insmod之后铺坞,可以看到/proc/devices下有了個名字為xxx的設(shè)備號
看一看應(yīng)用層
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
int main(int argc, const char *argv[])
{
int fd;
fd = open("/dev/hello", O_RDWR);
if(fd < 0)
{
perror("Failed to open.");
return -1;
}
else
{
printf("open success.\n");
}
getchar();
close(fd);
return 0;
}
編譯后看一下執(zhí)行結(jié)果
應(yīng)用層成功打開了該設(shè)備文件。按下回車鍵后就關(guān)閉了文件洲胖。
我們看看內(nèi)核層執(zhí)行的結(jié)果
成功的執(zhí)行了open和release函數(shù)济榨。
————————————————
版權(quán)聲明:本文為CSDN博主「念念有余」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議宾濒,轉(zhuǎn)載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/u012142460/article/details/78906576