国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
十一、Linux驅(qū)動程序開發(fā)(3) - 字符設(shè)備驅(qū)動(2)-globalmem設(shè)備驅(qū)動
下面將以linux設(shè)備驅(qū)動開發(fā)詳解上的globalmem設(shè)備驅(qū)動為例來詳細(xì)分析字符設(shè)備驅(qū) 動的過程。
#include <linux/module.h>//模塊所需的大量符號和函數(shù)定義
#include <linux/types.h>
#include <linux/fs.h>//文件系統(tǒng)相關(guān)的函數(shù)和頭文件
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>//包含驅(qū)動程序使用的大部分內(nèi)核API的定義,包括睡眠函數(shù)以及各種變量聲明
#include <linux/init.h>//指定初始化和清除函數(shù)
#include <linux/cdev.h>//cdev結(jié)構(gòu)的頭文件 包含<linux/kdev_t.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>//在內(nèi)核和用戶空間中移動數(shù)據(jù)的函數(shù)
#define GLOBALMEM_SIZE  0x1000  /*全局內(nèi)存最大4K字節(jié)*/
#define MEM_CLEAR 0x1  /*清0全局內(nèi)存*/
#define GLOBALMEM_MAJOR 254    /*預(yù)設(shè)的globalmem的主設(shè)備號*/
static int globalmem_major = GLOBALMEM_MAJOR;
/*globalmem設(shè)備結(jié)構(gòu)體*/
struct globalmem_dev
{
struct cdev cdev; /*cdev結(jié)構(gòu)體*/
unsigned char mem[GLOBALMEM_SIZE]; /*全局內(nèi)存*/
};
struct globalmem_dev *globalmem_devp; /*設(shè)備結(jié)構(gòu)體指針*/
/*文件打開函數(shù)*/
int globalmem_open(struct inode *inode, struct file *filp)
{
/*將設(shè)備結(jié)構(gòu)體指針賦值給文件私有數(shù)據(jù)指針.
系統(tǒng)在調(diào)用驅(qū)動程序的open方法前將這個指針置為NULL。
驅(qū)動程序可以將這個字段用于任意目的,也可以忽略這個字段。
驅(qū)動程序可以用這個字段指向已分配的數(shù)據(jù),
但是一定要在內(nèi)核釋放file結(jié)構(gòu)前的release方法中清除它*/
filp->private_data = globalmem_devp;
//將文件私有數(shù)據(jù)private_data指向設(shè)備結(jié)構(gòu)體,read,write,ioctl,llseek等函數(shù)
//通過private_data訪問設(shè)備結(jié)構(gòu)體
return 0;
}
/*文件釋放函數(shù)*/
int globalmem_release(struct inode *inode, struct file *filp)
{
return 0;
}
/*
備方法應(yīng)當(dāng)進(jìn)行下面的任務(wù):
1、釋放 open 分配在 filp->private_data 中的任何東西
2、在最后的 close 關(guān)閉設(shè)備globalmem_release的基本形式?jīng)]有硬件去關(guān)閉, 因此需要的代碼是最少的.不是每個 close 系統(tǒng)調(diào)用引起調(diào)用 release 方法. 只有真正釋放設(shè)備數(shù)據(jù)結(jié)構(gòu)的調(diào)用會調(diào)用這個方法
*/
/* ioctl設(shè)備控制函 數(shù) */
static int globalmem_ioctl(struct inode *inodep, struct file *filp, unsigned
int cmd, unsigned long arg)
{
struct globalmem_dev *dev = filp->private_data;/*獲得設(shè)備結(jié)構(gòu)體指針*/
switch (cmd)
{
case MEM_CLEAR://清除全局內(nèi)存
memset(dev->mem, 0, GLOBALMEM_SIZE);
printk(KERN_INFO "globalmem is set to zero\n");
break;
default://其他不支持的命令
return  - EINVAL;
}
return 0;
}
/*讀函數(shù)*/
static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t size,
loff_t *ppos)
{
unsigned long p =  *ppos;
unsigned int count = size;
int ret = 0;
struct globalmem_dev *dev = filp->private_data; /*獲得設(shè)備結(jié)構(gòu)體指針*/
/*分析和獲取有效的寫長度*/
if (p >= GLOBALMEM_SIZE)//0x1000 要讀的偏移位置越界
return count ?  - ENXIO: 0;
if (count > GLOBALMEM_SIZE - p)//要讀的字節(jié)數(shù)太大
count = GLOBALMEM_SIZE - p;
/*內(nèi)核空間->用戶空間*/
//將內(nèi)核空間的內(nèi)容復(fù)制到用戶空間,所復(fù)制的內(nèi)容是從from來,到to去,復(fù)制n個字節(jié)。
if (copy_to_user(buf, (void*)(dev->mem + p), count))
{
ret =  - EFAULT;
}
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "read %d bytes(s) from %d\n", count, p);
}
return ret;
}
/*寫函數(shù)*/
static ssize_t globalmem_write(struct file *filp, const char __user *buf,
size_t size, loff_t *ppos)
/*
filp 是文件指針,count 是請求的傳輸數(shù)據(jù)長度,buff 參數(shù)是指向用戶空間的緩沖區(qū),這個 緩沖區(qū)或者保存要寫入的數(shù)據(jù),或者是一個存放新讀入數(shù)據(jù)的空緩沖區(qū),該地址在內(nèi)核空間不能直接讀寫,offp 是一個指針指向一個"long offset type"對象, 它指出用戶正在存取的文件位置. 返回值是一個"signed size type。寫的位置相對于文件開頭的偏移。
*/
{
unsigned long p =  *ppos;
unsigned int count = size;
int ret = 0;
struct globalmem_dev *dev = filp->private_data; /*獲得設(shè)備結(jié)構(gòu)體指針*/
/*分析和獲取有效的寫長度*/
if (p >= GLOBALMEM_SIZE)//0x1000 要寫的偏移位置越界
return count ?  - ENXIO: 0; //???
if (count > GLOBALMEM_SIZE - p)//將數(shù)據(jù)寫入該緩存區(qū),直到結(jié)尾。這個是判斷要寫的字節(jié)數(shù)太多
count = GLOBALMEM_SIZE - p;
/*用戶空間->內(nèi)核空間*/
if (copy_from_user(dev->mem + p, buf, count))
//目的是從用戶空間拷貝數(shù)據(jù)到內(nèi)核空間,失敗返回沒有被拷貝的字節(jié)數(shù),成功返回0
ret =  - EFAULT;
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "written %d bytes(s) from %d\n", count, p);
}
return ret;//如果ret=count,則完成了所請求數(shù)目的字節(jié)傳送。
}
/* seek文件定位函數(shù) */
/*
seek()函數(shù)對文件定位的起始地址可以是文件開頭(SEEK_SET,0)、當(dāng)前位置(SEEK_CUR,1)和文件尾(SEEK_END,2),globalmem支持從文件開頭和當(dāng)前位置相對偏移。在定位的時候,應(yīng)該檢查用 戶請求的合法性,若不合法,函數(shù)返回- EINVAL,合法時返回文件的當(dāng)前位置*/
static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
{
loff_t ret = 0;
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;//struct file
ret = filp->f_pos;
break;
case 1:   /*相對文件當(dāng)前位置偏移*/
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;
break;
}
return ret;//合法時返回文件的當(dāng)前位置
}
/*文件操作結(jié)構(gòu)體*/
static const struct file_operations globalmem_fops =
{
.owner = THIS_MODULE,
.llseek = globalmem_llseek,
.read = globalmem_read,
.write = globalmem_write,
.ioctl = globalmem_ioctl,
.open = globalmem_open,
.release = globalmem_release,
};
/*初始化并注冊cdev
globalmem_setup_cdev(globalmem_devp, 0);*/
static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)
{
int err, devno = MKDEV(globalmem_major, index);
cdev_init(&dev->cdev, &globalmem_fops);
/*將 cdev 結(jié)構(gòu)嵌入一個你自己的設(shè)備特定的結(jié)構(gòu),你應(yīng)當(dāng)初始化你已經(jīng)分配的結(jié)構(gòu)使用以上函數(shù),有一個其他的 struct cdev 成員你需要初始化. 象 file_operations 結(jié)構(gòu),struct cdev 有一個擁 有者成員,應(yīng)當(dāng)設(shè)置為 THIS_MODULE,一旦 cdev 結(jié)構(gòu)建立, 最后的步驟是把它告訴內(nèi)核, 調(diào)用:
cdev_add(&dev->cdev, devno, 1);*/
/*void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev);
INIT_LIST_HEAD(&cdev->list);
cdev->kobj.ktype = &ktype_cdev_default;
kobject_init(&cdev->kobj);
cdev->ops = fops;
}  */
/*
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
/*globalmem設(shè)備結(jié)構(gòu)體*/
struct globalmem_dev
{
struct cdev cdev; /*cdev結(jié)構(gòu)體*/
unsigned char mem[GLOBALMEM_SIZE]; /*全局內(nèi)存*/
};
*/
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &globalmem_fops;
err = cdev_add(&dev->cdev, devno, 1);
/*
cdev 是 cdev 結(jié)構(gòu), devno 是這個設(shè)備響應(yīng)的第一個設(shè)備號, count 是應(yīng)當(dāng)關(guān) 聯(lián)到設(shè)備的設(shè)備號的數(shù)目. 常常 count 是 1, 但是有多個設(shè)備號對應(yīng)于一個特定的設(shè)備的情形. 例如, 設(shè)想 SCSI 磁帶驅(qū)動, 它允許用戶空間來選擇操作模式(例如密度), 通過安排多個次編號給每一個物理設(shè)備.在使用 cdev_add 是 有幾個重要事情要記住. 第一個是這個調(diào)用可能失敗. 如果它返回一個負(fù)的錯誤碼,
你的設(shè)備沒有增加到系統(tǒng)中. 它幾乎會一直成功, 但是, 并且?guī)鹆似渌狞c: cdev_add 一 返回, 你的設(shè)備就是"活的"并且內(nèi)核可以調(diào)用它的操作. 除非你的驅(qū)動完全準(zhǔn)備好處理設(shè)備上的操作, 你不應(yīng)當(dāng)調(diào)用 cdev_add */
if (err)
printk(KERN_NOTICE "Error %d adding LED%d", err, index);
}
/*設(shè)備驅(qū)動模塊加載函數(shù)*/
/*_init():__init是一個宏,編譯時會用到。對于靜態(tài)編譯在內(nèi)核的  模塊有意義.
內(nèi)核使用了大量不同的宏來標(biāo)記具有不同作用的函數(shù)和數(shù)據(jù)結(jié)構(gòu)。如 宏__init、__devinit等。
這些宏在include/linux/init.h頭文件中定義。編譯器通過這些宏可以把代碼優(yōu)化放到合適的內(nèi)存 位置,
以減少內(nèi)存占用和提高內(nèi)核效率。
init():標(biāo)記內(nèi)核啟動時使用的初始化代碼,內(nèi)核啟動完成后不再需要。以此標(biāo)記的代碼位于.init.text內(nèi)存區(qū)域。
#define _ _init    _ _attribute_ _ ((_ _section_ _ (".text.init")))
初始化代碼的特點是:在系統(tǒng)啟動運行,且一旦運行后馬上退出內(nèi) 存,不再占用內(nèi)存。
*/
int globalmem_init(void)
{
int result;
dev_t devno = MKDEV(globalmem_major, 0);
/*通過主次設(shè)備號生成dev_t.
主次設(shè)備號的數(shù)據(jù)類型是dev_t,在/linux/types.h中定義。
在2.6內(nèi)核中,dev_t是一個32位的數(shù),其中12位用來表示主設(shè)備號,
其余20位用來表示次設(shè)備號。要獲得設(shè)備的主次設(shè)備號可以使用內(nèi)核提供的宏:
MAJOR(dev_t dev);        #獲得主設(shè)備號
MINOR(dev_t dev);        #獲得次設(shè)備號
這些宏定義位于linux/kdev_t.h中。如果要把主次設(shè)備號轉(zhuǎn)換成dev_t類型,
則可使用:
MKDEV(int major, int minor);*/
/* 申請設(shè)備號*/
if (globalmem_major)/*靜態(tài)申請設(shè)備號*/
result = register_chrdev_region(devno, 1, "globalmem");
/*在linux/fs.h中,
devno:是你要分配的起始設(shè)備編號. devno的次編號部分常常是 0, 但是沒有要求是那個效果;
1:是所請求的連續(xù)設(shè)備號的個數(shù);
globalmem:是和該設(shè)備號范圍關(guān)聯(lián)的設(shè)備名稱,它將出現(xiàn)在/proc/devices或/sysfs中。
如果分配成功則返回0,分配失敗則返回一個負(fù)的錯誤碼,所請求的設(shè)備號無效。
*/
else  /* 動態(tài)申請設(shè)備號*/
{
result = alloc_chrdev_region(&devno, 0, 1, "globalmem");
/*dev_t *dev
dev:是一個只輸出的參數(shù), 它在函數(shù)成功完成時持有你的分配范圍的第一個數(shù);
firstminor:應(yīng)當(dāng)是請求的第一個要用的次編號; 它常常是 0;
1:     是所請求的連續(xù)設(shè)備號的個數(shù);
Globalmem:是和該設(shè)備號范圍關(guān)聯(lián)的設(shè)備名稱,它將出現(xiàn)在/proc/devices或/sysfs中。*/
globalmem_major = MAJOR(devno);/*獲得主設(shè)備號,從dev_t結(jié)構(gòu)中分解出主設(shè)備號*/
}
/*
如果globalmem_major=0,利用udev工具自動向系統(tǒng)動態(tài)申請未被占用的設(shè)備號相關(guān)函數(shù)定義在include/linux/kdev_t.h中
#define MINORBITS       20
#define MINORMASK   ((1U << MINORBITS) - 1)
//(1<<20 -1) 此操作后,MINORMASK宏的低20位為1,高12位為0
#define MAJOR(dev)       ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev)       ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi)  (((ma) << MINORBITS) | (mi))
*/
if (result < 0)
return result;
/* 動態(tài)申請設(shè)備結(jié)構(gòu)體的內(nèi)存*/
globalmem_devp = kmalloc(sizeof(struct globalmem_dev), GFP_KERNEL);
/*
在設(shè)備驅(qū)動程序中動態(tài)開辟內(nèi)存,不是用malloc,而是kmalloc,或者用get_free_pages直接申請頁。釋放內(nèi)存用的是kfree,或free_pages. 請注意,kmalloc等函數(shù)返回的是物理地址!而malloc等返回的是線性地址!要注意kmalloc最大只能開辟128k-16,16個字節(jié)是被頁描述符結(jié)構(gòu)占用了。內(nèi)存映 射的I/O口,寄存器或者是硬件設(shè)備的RAM(如顯存)一般占用F0000000以上的地址空間。在驅(qū)動程序中不能直接訪問,要通過kernel函數(shù)vremap獲得重新映射以后的地址*/
if (!globalmem_devp)    /*申請失敗*/
{
result =  - ENOMEM;
goto fail_malloc;
}
memset(globalmem_devp, 0, sizeof(struct globalmem_dev));
/*memset會將參數(shù)globalmem_devp所指的內(nèi)存區(qū)域中前sizeof(struct globalmem_dev)個字節(jié)以參數(shù)0填入,然后返回指向globalmem_devp的指針*/
globalmem_setup_cdev(globalmem_devp, 0);
/*初始化并注冊cdev*/
return 0;
fail_malloc: unregister_chrdev_region(devno, 1);
/*釋放原先申請的設(shè)備號
如果我們不再使用設(shè)備號,則要使用unregister_chrdev_region()函數(shù)釋放它。
devno:是要分配的主設(shè)備號范圍的起始值,次 設(shè)備號一般設(shè)置為0;
1:是所請求的連續(xù)設(shè)備號的個數(shù);*/
return result;
}
/*模塊卸載函數(shù)*/
/*
__exit,標(biāo)記退出代碼,對于非模塊無效。
*/
void globalmem_exit(void)
{
cdev_del(&globalmem_devp->cdev);   /*注 銷cdev,為從系統(tǒng)去除一個字符設(shè)備*/
kfree(globalmem_devp);     /*釋放設(shè)備結(jié)構(gòu)體內(nèi)存*/
unregister_chrdev_region(MKDEV(globalmem_major, 0), 1);
/*釋放設(shè)備號
我們一般在模塊的清除函數(shù)中調(diào)用設(shè)備號釋放函數(shù)。*/
}
MODULE_AUTHOR("Song Baohua");
MODULE_LICENSE("Dual BSD/GPL");//指定代碼使用的許可證
module_param(globalmem_major, int, S_IRUGO);
/*module_param宏的第一個參數(shù)是選項名,可在/sys虛擬文件系統(tǒng)中該模塊的parameter目錄中中查看到。第二個參數(shù)是選項類型,第三個參數(shù)是選項的值*/
module_init(globalmem_init);
module_exit(globalmem_exit);
/*向操作系統(tǒng)注冊自己定義的這兩個函數(shù),該項目在Kconfig中配置項目為布爾型的話為Y和N兩種選項,Y為編譯進(jìn)內(nèi)核,N不編譯,如果為三態(tài)型(tristate),為Y,N,M三種選項M為編譯為模塊,模塊也可以在內(nèi)核編譯后根據(jù)需要進(jìn)行手動加載,也可以寫個腳本自動加載*/
總結(jié):
字符設(shè)備是3大類設(shè)備(字符設(shè)備、塊設(shè)備和網(wǎng)絡(luò)設(shè)備)中較簡單的一類設(shè)備,其驅(qū)動程序中完成的主要工作是初始化、添加和刪除cdev結(jié)構(gòu)體,申請和釋放設(shè)備號,以及填充file_opersation結(jié)構(gòu)體中的操作函數(shù),實現(xiàn)file_opersation結(jié)構(gòu)體中的read()、write()和ioctl()等函數(shù)是驅(qū)動設(shè)計的主體工作。
本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊舉報
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
Linux驅(qū)動開發(fā)庖丁解牛之三——揭開字符設(shè)備驅(qū)動程序的面紗
Linux內(nèi)核開發(fā)之簡單字符設(shè)備驅(qū)動(上)
Linux 設(shè)備驅(qū)動 ====> 并發(fā)控制
linux設(shè)備驅(qū)動歸納總結(jié)(三):3.設(shè)備驅(qū)動面向?qū)ο笏枷牒蚻seek的實現(xiàn)
【原創(chuàng)】Linux select/poll機制原理分析
linux字符設(shè)備驅(qū)動模版本 - hisashi的日志 - 網(wǎng)易博客
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服