linux字符設(shè)備驅(qū)動基礎(chǔ)
1.字符設(shè)備驅(qū)動相關(guān)系統(tǒng)函數(shù)簡介
1.1 container_of
/**
* container_of 通過一個結(jié)構(gòu)體成員的指針,獲取包含該成員的結(jié)構(gòu)體的起始地址
* @ptr: 變量的指針
* @type: 指針指向的結(jié)構(gòu)體類型
* @member: 結(jié)構(gòu)體中的變量類型
*/
#define container_of(ptr, type, member) ({ \
void *__mptr = (void *)(ptr); \
BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) && \
!__same_type(*(ptr), void), \
"pointer type mismatch in container_of"); \
((type *)(__mptr - offsetof(type, member))); })為了簡化理解,可以將 container_of 函數(shù)理解為如下表示
#define container_of(ptr, type, member) \
((type *)((char *)(ptr) - offsetof(type, member)))該函數(shù)根據(jù) 結(jié)構(gòu)體成員變量的指針,通過該變量相對于結(jié)構(gòu)體的偏移,得到了該變量對應(yīng)的結(jié)構(gòu)體的指針。
這是一個非常靈活的用法,通過保存某個結(jié)構(gòu)體變量的指針,可以通過該指針反向推出該結(jié)構(gòu)體的指針。在后續(xù)案例中有詳細(xì)解釋。
1.2 register_chrdev_region
register_chrdev_region 是 Linux 內(nèi)核中用于注冊字符設(shè)備編號范圍的函數(shù)。該函數(shù)為驅(qū)動程序預(yù)留一段連續(xù)的設(shè)備號(主設(shè)備號 + 起始次設(shè)備號),后續(xù)將字符設(shè)備(通過 cdev_add )綁定到這些設(shè)備號上,函數(shù)原型如下
/** * first:dev_t 類型,指定要注冊的起始設(shè)備號。使用 MKDEV(major, minor) 宏生成 * count:需要注冊的連續(xù)設(shè)備號數(shù)量(次設(shè)備號的范圍) * name:設(shè)備的名稱,該名稱會出現(xiàn)在 /proc/devices 文件中,用于標(biāo)識該組設(shè)備號屬于哪個驅(qū)動。 * 返回值: 成功返回0 * 參數(shù)無效返回 -EINVAL * 設(shè)備號被占用返回 -EBUSY */ int register_chrdev_region(dev_t first, unsigned int count, const char *name);
1.3 cdev_init
struct cdev 是內(nèi)核表示字符設(shè)備的對象,每個字符設(shè)備驅(qū)動都需要創(chuàng)建 cdev 實例,并將其注冊到內(nèi)核
cdev_init 負(fù)責(zé)設(shè)置 cdev 的基本字段,為后續(xù)的 cdev_add做準(zhǔn)備
/*
* dev:指向要初始化的 struct cdev 結(jié)構(gòu)體的指針。
* 該結(jié)構(gòu)體可以由驅(qū)動靜態(tài)分配 (kmalloc) ,也可以動態(tài)分配 (cdev_alloc)
* fops:指向 struct file_operations 結(jié)構(gòu)體的指針
* 該結(jié)構(gòu)體包含了設(shè)備支持的各種操作函數(shù)(如 open、read、write、ioctl 等)
*/
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev);
INIT_LIST_HEAD(&cdev->list);
kobject_init(&cdev->kobj, &ktype_cdev_default);
cdev->ops = fops;
}1.4 cdev_add
該函數(shù)建立了設(shè)備號與字符設(shè)備驅(qū)動的關(guān)聯(lián),使 VFS(虛擬文件系統(tǒng))能夠通過設(shè)備號找到對應(yīng)的驅(qū)動程序,從而響應(yīng)用戶空間的打開、讀寫等操作
- 將 struct cdev 對象與設(shè)備號綁定,在內(nèi)核的字符設(shè)備映射表中建立關(guān)聯(lián)
- 將 cdev 對象加入內(nèi)核的全局字符設(shè)備鏈表或哈希表,使 VFS 能夠根據(jù)設(shè)備號找到對應(yīng)的cdev
- 激活設(shè)備:調(diào)用 cdev_add 后,該設(shè)備便可以被用戶空間訪問(需要使用
mknod關(guān)聯(lián)到/dev/mydevname)
/*
* p:指向 cdev_init() 已經(jīng)初始化的 struct cdev 對象的指針。
* dev:分配給該設(shè)備的起始設(shè)備號,應(yīng)該與 register_chrdev_region() 向內(nèi)核申請的設(shè)備號位置一致
* count:與該設(shè)備關(guān)聯(lián)的次設(shè)備號數(shù)量
* 返回值:成功 0
參數(shù)無效 -EINVAL
設(shè)備號被占用 -EBUSY (理論上在分配設(shè)備號時已經(jīng)檢查過,但這里仍可能發(fā)生,如動態(tài)添加時競爭)
內(nèi)存不足 -ENOMEM
*/
int cdev_add(struct cdev *p, dev_t dev, unsigned count);2. 使用字符設(shè)備驅(qū)動實現(xiàn)共享內(nèi)存
下面是使用字符設(shè)備驅(qū)動實現(xiàn)共享內(nèi)存的案例,用來快速熟悉驅(qū)動函數(shù)的使用方法,代碼參考書籍為Linux設(shè)備驅(qū)動詳解
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/module.h>
#include <linux/ioctl.h>
#include <linux/io.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#define GLOBALMEM_SIZE 1024
#define GLOBALMEM_MAGIC 'M'
#define MEM_CLEAR _IO(GLOBALMEM_MAGIC, 0)
struct globalmem_dev
{
// 字符設(shè)備
struct cdev m_cdev;
// 共享內(nèi)存
unsigned char mem[GLOBALMEM_SIZE];
};
static int globalmem_major = 266;
// 存放兩個字符設(shè)備私有數(shù)據(jù)
struct globalmem_dev* globalmem_devp;
/* user open fd */
static int globalmem_open(struct inode* inode, struct file* filp) {
struct globalmem_dev* dev;
/* 下面是一種常用的區(qū)分次設(shè)備號的方法
* 通過 globalmem_init 初始化時傳入不同的 cdev 指針實現(xiàn)區(qū)分 */
// inode 中保存的 i_cdev 指針是 globalmem_init 函數(shù)中傳入的,globalmem_dev 結(jié)構(gòu)體變量 m_cdev 的指針
// 所以通過 inode->i_cdev 指針,即 m_cdev 成員變量的指針,可以找到其對應(yīng)的結(jié)構(gòu)體指針
dev = container_of(inode->i_cdev, struct globalmem_dev, m_cdev);
// 將設(shè)備結(jié)構(gòu)體指針傳給文件私有數(shù)據(jù)指針,提供給其他函數(shù)調(diào)用時使用
filp->private_data = dev;
return 0;
}
/* user release fd*/
static int globalmem_release(struct inode* inode, struct file* filp) {
return 0;
}
/* user read fd */
static ssize_t globalmem_read(struct file* filp, char __user* buf, size_t count, loff_t* ppos) {
unsigned long p = *ppos;
// 獲得設(shè)備結(jié)構(gòu)體的指針(這是通過open函數(shù)傳入的指針,實現(xiàn)不同次設(shè)備使用不同的共享內(nèi)存)
struct globalmem_dev* dev = filp->private_data;
if(p >= GLOBALMEM_SIZE)
return 0;
if(count > GLOBALMEM_SIZE - p)
count = GLOBALMEM_SIZE - p;
copy_to_user(buf, (void*)(dev->mem + p), count);
*ppos = p + count;
return count;
}
/* user write fd */
static ssize_t globalmem_write(struct file* filp, const char __user* buf, size_t count, loff_t* ppos) {
unsigned long p = *ppos;
// 獲得設(shè)備結(jié)構(gòu)體的指針
struct globalmem_dev* dev = filp->private_data;
if(p >= GLOBALMEM_SIZE)
return 0;
if(count > GLOBALMEM_SIZE - p)
count = GLOBALMEM_SIZE - p;
copy_from_user(dev->mem + p, buf, count);
*ppos = p + count;
return count;
}
/* user lseek fd */
static loff_t globalmem_llseek(struct file* filp, loff_t offset, int orig) {
loff_t ret;
switch(orig) {
// 從起始位置開始移動指針
case 0:
if(offset < 0) {
ret = -EINVAL;
break;
}
if((unsigned int)offset > GLOBALMEM_SIZE) {
ret = -EINVAL;
break;
}
filp->f_pos = (unsigned int)offset;
ret = filp->f_pos;
break;
// 從當(dāng)前位置開始移動指針
case 1:
if((filp->f_pos + offset) > GLOBALMEM_SIZE) {
ret = -EINVAL;
break;
}
if((filp->f_pos + offset) < 0) {
ret = -EINVAL;
break;
}
filp->f_pos += offset;
ret = filp->f_pos;
break;
default:
ret = -EINVAL;
}
return ret;
}
/* user ioctl fd */
static int globalmem_ioctl(struct inode* inodep, struct file* filp, unsigned int cmd, unsigned long arg) {
// 獲取設(shè)備結(jié)構(gòu)體指針
struct globalmem_dev* dev = filp->private_data;
switch(cmd) {
case MEM_CLEAR:
memset(dev->mem, 0, GLOBALMEM_SIZE);
break;
default:
return -EINVAL;
}
return 0;
}
static const struct file_operations globalmem_fops = {
.owner = THIS_MODULE,
.open = globalmem_open,
.release = globalmem_release,
.llseek = globalmem_llseek,
.read = globalmem_read,
.write = globalmem_write,
.unlocked_ioctl = globalmem_ioctl
};
/* 設(shè)備驅(qū)動模塊insmod加載函數(shù) */
static int globalmem_init(void) {
// 向 Linux 內(nèi)核中注冊字符設(shè)備編號范圍
register_chrdev_region(MKDEV(globalmem_major, 0), 2, "globalmem");
// 為2個次設(shè)備以及共享內(nèi)存分配內(nèi)存
globalmem_devp = kmalloc(2 * sizeof(struct globalmem_dev), GFP_KERNEL);
memset(globalmem_devp, 0, 2 * sizeof(struct globalmem_dev));
// 初始化字符設(shè)備0的基本字段
cdev_init(&(globalmem_devp[0].m_cdev), &globalmem_fops);
globalmem_devp[0].m_cdev.owner = THIS_MODULE;
// 將主設(shè)備號globalmem_major次設(shè)備號0,與字符設(shè)備驅(qū)動的關(guān)聯(lián)
cdev_add(&(globalmem_devp[0].m_cdev), MKDEV(globalmem_major, 0), 1);
// 初始化字符設(shè)備1的基本字段
cdev_init(&(globalmem_devp[1].m_cdev), &globalmem_fops);
globalmem_devp[1].m_cdev.owner = THIS_MODULE;
// 將主設(shè)備號globalmem_major次設(shè)備號1,與字符設(shè)備驅(qū)動的關(guān)聯(lián)
cdev_add(&(globalmem_devp[1].m_cdev), MKDEV(globalmem_major, 1), 1);
return 0;
}
static int globalmem_exit(void) {
// 注銷cdev
cdev_del(&(globalmem_devp[0].m_cdev));
cdev_del(&(globalmem_devp[1].m_cdev));
// 釋放設(shè)備結(jié)構(gòu)體內(nèi)存
kfree(globalmem_devp);
// 釋放設(shè)備號
dev_t devno = MKDEV(globalmem_major, 0);
unregister_chrdev_region(devno, 2);
}
MODULE_AUTHOR("cear");
MODULE_LICENSE("GPL");
module_param(globalmem_major, int, S_IRUGO);
module_init(globalmem_init);
module_exit(globalmem_exit);到此這篇關(guān)于linux字符設(shè)備驅(qū)動基礎(chǔ)的文章就介紹到這了,更多相關(guān)linux字符設(shè)備驅(qū)動內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Linux Crontab Shell腳本實現(xiàn)秒級定時任務(wù)的方法
這篇文章主要介紹了Linux Crontab Shell腳本實現(xiàn)秒級定時任務(wù)的方法,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2018-11-11
Linux統(tǒng)計目錄下文件個數(shù)的常見命令總結(jié)
在?Linux?系統(tǒng)中,常常需要了解某個目錄中包含多少文件,這不僅有助于管理文件,也能夠幫助我們分析目錄結(jié)構(gòu)、存儲空間的使用情況,下面我們就來看看有哪些方法吧2025-11-11
linux系統(tǒng)AutoFs自動掛載服務(wù)安裝配置
大家好,本篇文章主要講的是linux系統(tǒng)AutoFs自動掛載服務(wù)安裝配置,感興趣的同學(xué)趕快來看一看吧,對你有幫助的話記得收藏一下哦,方便下次瀏覽2021-12-12
Windows 配置Apache以便在瀏覽器中運行Python script的CGI模式
在前面的我的一篇文章中 “Windows XP下的Python 首次安裝配置和使用 ”談到當(dāng)想在Apache服務(wù)器下運行Python script的時候,發(fā)現(xiàn)Apache的mod_python版本還不支持Python 2.6更別說3.0.1了,只有2.5之下的,折騰著卸載和安裝,最后還沒搞定,就先擱一邊了。2009-07-07
解決Ubuntu下使用linuxdeployqt打包Qt程序問題
這篇文章主要介紹了解決Ubuntu下使用linuxdeployqt打包Qt程序問題,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下2019-07-07
cloudera manager 設(shè)置開機自啟的方法
下面小編就為大家?guī)硪黄猚loudera manager 設(shè)置開機自啟的方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-01-01
Linux(Ubuntu)系統(tǒng)上傳項目到GitHub新建倉庫過程
該文主要講述了如何創(chuàng)建SSHkeys,設(shè)置本地git的用戶名和郵箱,連接GitHub賬號,上傳項目到GitHub上等整個過程,具體步驟包括:創(chuàng)建SSHkeys、設(shè)置本地git信息、連接GitHub賬號、創(chuàng)建GitHub倉庫、上傳項目等2026-03-03

