介紹
字符設備:是指只能一個字節一個字節讀寫的設備,不能隨機讀取設備內存中的某一數據,讀取數據需要按照先後數據。字符設備是面向流的設備,常見的字符設備有鼠標、鍵盤、串口、控制檯和LED設備等
每一個字符設備或塊設備都在/dev目錄下對應一個設備文件。linux用戶程序通過設備文件(或稱設備節點)來使用驅動程序操作字符設備和塊設備。
字符設備驅動模型
1、cdev結構體
struct cdev {
struct kobject kobj; /*內嵌的kobject結構,用於內核設備驅動模型的管理*/
struct module *owner; /*指向包含該結構的模塊的指針,用於引用計數*/
const struct file_operations *ops; /*指向字符設備操作函數集的指針*/
struct list_head list; /*該結構將使用該驅動的字符設備連接成一個鏈表*/
/*與cdev對應的字符設備文件的inode->i_devices的鏈表頭*/
dev_t dev; /*該字符設備的起始設備號,一個設備可能有多個設備號*/
unsigned int count; /*使用該字符設備驅動的設備數量*/
};
cdev 結構體的dev_t 成員定義了設備號,爲32位,其中12位是主設備號,20位是次設備號
我們只需使用二個簡單的宏就可以從dev_t 中獲取主設備號和次設備號:
MAJOR(dev_t dev)
MINOR(dev_t dev)
相反地,可以通過主次設備號來生成dev_t:
MKDEV(int major,int minor)
2、分配cdev
struct cdev btn_cdev;
/*申請設備號,如果申請失敗採用動態申請方式*/
if(major)
{
//靜態
dev_id = MKDEV(major, 0);
register_chrdev_region(dev_id, 1, "button");
}
else
{
//動態
alloc_chardev_region(&dev_id, 0, 1, "button");
major = MAJOR(dev_id);
}
靜態申請:
int register_chrdev_region(dev_t from, unsigned count, const char *name);
/*功能:申請使用從from開始的count個設備號(主設備號不變,次設備號增加)*/
from :要分配的設備編號範圍的初始值(次設備號常設爲0);
Count:連續編號範圍.
name:編號相關聯的設備名稱. (/proc/devices);
靜態申請相對較簡單,但是一旦驅動被廣泛使用,這個隨機選定的主設備號可能會導致設備號衝突,而使驅動程序無法註冊。
動態申請:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name);
/*功能:請求內核動態分配count個設備號,且次設備號從baseminor開始。*/
//返回值小於0表示分配失敗,系統自動返回沒有佔用的設備號
baseminor:是請求的最小的次編號;
count:是請求的連續設備編號的總數;
name:爲設備名
然後通過major=MMOR(dev)獲取主設備號
動態申請簡單,易於驅動推廣,但是無法在安裝驅動前創建設備文件(因爲安裝前還沒有分配到主設備號)。
銷燬設備號
void unregister_chrdev_region(dev_t first, unsigned int count);
first爲第一個設備號,count爲申請的設備數量
3、初始化c_dev
內核在內部使用類型 struct cdev 的結構來代表字符設備.有 2 種方法來分配和初始化一個這些結構.
①如果你想在運行時獲得一個獨立的 cdev 結構, 你可以爲此使用這樣的代碼:
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &my_fops;
②另一種是把cdv結構嵌入到你自己封裝的設備結構中,這時需要使用下面的方法來分配和初始化:
void cdev_init(struct cdev *cdev, struct file_operations *fops);
cdev_ init()函數用於初始化 cdev 的成員,並建立 cdev 和 file_operations 之間的連接。
cdev_init()操作源代碼:
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;
}
4、註冊cdev
int cdev_add(struct cdev *dev, dev_t num, unsigned int count)
//向系統添加一個cdev,完成字符設備的註冊。
dev 是 cdev 結構, num 是這個設備響應的第一個設備號, count 是應當關聯到設備的設備號的數目. 常常 count 是 1。
5、硬件初始化
6、設備操作
file_operations結構
struct file_operations {
struct module *owner; //THIS_MODULE, <linux/module.h> 中定義的宏.
/*用於修改文件當前的讀寫位置*/
loff_t(*llseek) (struct file *, loff_t, int);
ssize_t(*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t(*aio_read) (struct kiocb *, char __user *, size_t, loff_t);
ssize_t(*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t(*aio_write) (struct kiocb *, const char __user *, size_t, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, 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(*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t(*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t(*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void __user *);
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);
};
file結構,文件結構代表一個打開的文件
struct file{
mode_t fmode; //文件模式,如FMODE_READ,FMODE_WRITE*/
......
loff_t f_pos; //當前讀寫位置.off_t是一個64位的數,驅動可以讀這個值,如果它需要知道文件中的當前位置,但是正常地不應該改變它。
unsigned int f_flags; //文件標誌,例如O_RDONLY,O_NONBLOCK,和O_SYNC.驅動應當檢查 O_NONBLOCK標誌來看是否是請求非阻塞操作
struct file_operations *f_op;
void *private_data; //非常重要,用於存放轉換後的設備描述結構指針*/
.......
};
inode 結構
內核用inode 結構在內部表示文件,它是實實在在的表示物理硬件上的某一個文件,且一個文件僅有一個inode與之對應,同樣它有二個比較重要的成員:
struct inode{
dev_t i_rdev; //設備編號
struct cdev *i_cdev;
};
//從inode中獲取主次設備號
unsigned int imajor(struct inode *inode);
unsigned int iminor(struct inode *inode);
7、字符設備驅動模塊加載與卸載函數
在字符設備驅動模塊加載函數中應該實現設備號的申請和cdev 結構的註冊
在卸載函數中應該實現設備號的釋放與cdev結構的註銷。
我們一般習慣將cdev內嵌到另外一個設備相關的結構體裏面,該設備包含所涉及的cdev、私有數據及信號量等等信息。常見的設備結構體、模塊加載函數、模塊卸載函數形式如下:
/*設備結構體*/
struct xxx_dev{
struct cdev cdev;
char *data;
struct semaphore sem;
......
};
/*模塊加載函數*/
static int __init xxx_init(void)
{
.......
初始化cdev結構;
申請設備號;
註冊設備號;
申請分配設備結構體的內存;
/*非必須*/
}
/*模塊卸載函數*/
static void __exit xxx_exit(void)
{
.......
釋放原先申請的設備號;
釋放原先申請的內存;
註銷cdev設備;
}
實例1
#ifndef MEMDEV_MAJOR
#define MEMDEV_MAJOR 251 /*預設的mem的主設備號*/
#endif
#ifndef MEMDEV_NR_DEVS
#define MEMDEV_NR_DEVS 2 /*設備數*/
#endif
#ifndef MEMDEV_SIZE
#define MEMDEV_SIZE 4096
#endif
/*mem設備描述結構體*/
struct mem_dev
{
char *data;
unsigned long size;
};
static mem_major = MEMDEV_MAJOR;
module_param(mem_major, int, S_IRUGO);
struct mem_dev *gp_mem_dev; /*設備結構體指針*/
struct cdev g_cdev;
/*文件打開函數*/
int mem_open(struct inode *inode, struct file *filp)
{
struct mem_dev *dev;
/*獲取次設備號*/
int num = MINOR(inode->i_rdev);
if (num >= MEMDEV_NR_DEVS)
return -ENODEV;
dev = &gp_mem_dev[num];
/*將設備描述結構指針賦值給文件私有數據指針*/
filp->private_data = dev;
return 0;
}
/*文件釋放函數*/
int mem_release(struct inode *inode, struct file *filp)
{
return 0;
}
/*讀函數*/
static ssize_t mem_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 mem_dev *dev = filp->private_data; /*獲得設備結構體指針*/
/*判斷讀位置是否有效*/
if (p >= MEMDEV_SIZE) /*要讀取的偏移大於設備的內存空間*/
return 0;
if (count > MEMDEV_SIZE - p) /*要讀取的字節大於設備的內存空間*/
count = MEMDEV_SIZE - p;
/*讀數據到用戶空間:內核空間->用戶空間交換數據*/
if (copy_to_user(buf, (void*)(dev->data + p), count))
{
ret = - EFAULT;
}
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "read %d bytes(s) from %d\n", count, p);
}
return ret;
}
/*寫函數*/
static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
struct mem_dev *dev = filp->private_data; /*獲得設備結構體指針*/
/*分析和獲取有效的寫長度*/
if (p >= MEMDEV_SIZE)
return 0;
if (count > MEMDEV_SIZE - p) /*要寫入的字節大於設備的內存空間*/
count = MEMDEV_SIZE - p;
/*從用戶空間寫入數據*/
if (copy_from_user(dev->data + p, buf, count))
ret = - EFAULT;
else
{
*ppos += count; /*增加偏移位置*/
ret = count; /*返回實際的寫入字節數*/
printk(KERN_INFO "written %d bytes(s) from %d\n", count, p);
}
return ret;
}
/* seek文件定位函數 */
static loff_t mem_llseek(struct file *filp, loff_t offset, int whence)
{
loff_t newpos;
switch(whence) {
case 0: /* SEEK_SET */ /*相對文件開始位置偏移*/
newpos = offset; /*更新文件指針位置*/
break;
case 1: /* SEEK_CUR */
newpos = filp->f_pos + offset;
break;
case 2: /* SEEK_END */
newpos = MEMDEV_SIZE -1 + offset;
break;
default: /* can't happen */
return -EINVAL;
}
if ((newpos<0) || (newpos>MEMDEV_SIZE))
return -EINVAL;
filp->f_pos = newpos;
return newpos;
}
/*文件操作結構體*/
static const struct file_operations mem_fops =
{
.owner = THIS_MODULE,
.llseek = mem_llseek,
.read = mem_read,
.write = mem_write,
.open = mem_open,
.release = mem_release,
};
/*設備驅動模塊加載函數*/
static int memdev_init(void)
{
int result;
int i;
dev_t devno = MKDEV(mem_major, 0);
/* 申請設備號,當xxx_major不爲0時,表示靜態指定;當爲0時,表示動態申請*/
/* 靜態申請設備號*/
if(mem_major)
result = register_chrdev_region(devno, 2, "memdev");
else /* 動態分配設備號 */
{
result = alloc_chrdev_region(&devno, 0, 2, "memdev");
mem_major = MAJOR(devno); /*獲得申請的主設備號*/
}
if (result < 0)
return result;
/*初始化cdev結構,並傳遞file_operations結構指針*/
cdev_init(&g_cdev, &mem_fops);
g_cdev.owner = THIS_MODULE; /*指定所屬模塊*/
g_cdev.ops = &mem_fops;
/*註冊字符設備*/
cdev_add(&g_cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS);
/*爲設備描述結構分配內存*/
gp_mem_dev = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);
if (!gp_mem_dev) /*申請失敗*/
{
result = - ENOMEM;
goto fail_malloc;
}
memset(gp_mem_dev, 0, sizeof(struct mem_dev));
/*爲設備分配內存*/
for (i=0; i < MEMDEV_NR_DEVS; i++)
{
gp_mem_dev[i].size = MEMDEV_SIZE;
gp_mem_dev[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);
memset(gp_mem_dev[i].data, 0, MEMDEV_SIZE);
}
return 0;
fail_malloc:
unregister_chrdev_region(devno, 1);
return result;
}
/*模塊卸載函數*/
static void memdev_exit(void)
{
cdev_del(&g_cdev); /*註銷設備*/
kfree(gp_mem_dev); /*釋放設備結構體內存*/
unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*釋放設備號*/
}
MODULE_AUTHOR("David Xie");
MODULE_LICENSE("GPL");
module_init(memdev_init);
module_exit(memdev_exit);
Makefile文件如下:
ifneq ($(KERNELRELEASE),)
obj-m := cdevdemo.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions modules.order Module.symvers
實例2
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/timer.h>
#include <asm/atomic.h>
#include <linux/slab.h>
#include <linux/device.h>
#define CDEVDEMO_MAJOR 255 /*預設cdevdemo的主設備號*/
static int cdevdemo_major = CDEVDEMO_MAJOR;
/*設備結構體,此結構體可以封裝設備相關的一些信息等
信號量等也可以封裝在此結構中,後續的設備模塊一般都
應該封裝一個這樣的結構體,但此結構體中必須包含某些
成員,對於字符設備來說,我們必須包含struct cdev cdev*/
struct cdevdemo_dev
{
struct cdev cdev;
};
struct cdevdemo_dev *cdevdemo_devp; /*設備結構體指針*/
/*文件打開函數,上層對此設備調用open時會執行*/
int cdevdemo_open(struct inode *inode, struct file *filp)
{
printk(KERN_NOTICE "======== cdevdemo_open ");
return 0;
}
/*文件釋放,上層對此設備調用close時會執行*/
int cdevdemo_release(struct inode *inode, struct file *filp)
{
printk(KERN_NOTICE "======== cdevdemo_release ");
return 0;
}
/*文件的讀操作,上層對此設備調用read時會執行*/
static ssize_t cdevdemo_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
printk(KERN_NOTICE "======== cdevdemo_read ");
}
/* 文件操作結構體,文中已經講過這個結構*/
static const struct file_operations cdevdemo_fops =
{
.owner = THIS_MODULE,
.open = cdevdemo_open,
.release = cdevdemo_release,
.read = cdevdemo_read,
};
/*初始化並註冊cdev*/
static void cdevdemo_setup_cdev(struct cdevdemo_dev *dev, int index)
{
printk(KERN_NOTICE "======== cdevdemo_setup_cdev 1");
int err, devno = MKDEV(cdevdemo_major, index);
printk(KERN_NOTICE "======== cdevdemo_setup_cdev 2");
/*初始化一個字符設備,設備所支持的操作在cdevdemo_fops中*/
cdev_init(&dev->cdev, &cdevdemo_fops);
printk(KERN_NOTICE "======== cdevdemo_setup_cdev 3");
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &cdevdemo_fops;
printk(KERN_NOTICE "======== cdevdemo_setup_cdev 4");
err = cdev_add(&dev->cdev, devno, 1);
printk(KERN_NOTICE "======== cdevdemo_setup_cdev 5");
if(err)
{
printk(KERN_NOTICE "Error %d add cdevdemo %d", err, index);
}
}
int cdevdemo_init(void)
{
printk(KERN_NOTICE "======== cdevdemo_init ");
int ret;
dev_t devno = MKDEV(cdevdemo_major, 0);
struct class *cdevdemo_class;
/*申請設備號,如果申請失敗採用動態申請方式*/
if(cdevdemo_major)
{
printk(KERN_NOTICE "======== cdevdemo_init 1");
ret = register_chrdev_region(devno, 1, "cdevdemo");
}else
{
printk(KERN_NOTICE "======== cdevdemo_init 2");
ret = alloc_chrdev_region(&devno,0,1,"cdevdemo");
cdevdemo_major = MAJOR(devno);
}
if(ret < 0)
{
printk(KERN_NOTICE "======== cdevdemo_init 3");
return ret;
}
/*動態申請設備結構體內存*/
cdevdemo_devp = kmalloc(sizeof(struct cdevdemo_dev), GFP_KERNEL);
if(!cdevdemo_devp) /*申請失敗*/
{
ret = -ENOMEM;
printk(KERN_NOTICE "Error add cdevdemo");
goto fail_malloc;
}
memset(cdevdemo_devp,0,sizeof(struct cdevdemo_dev));
printk(KERN_NOTICE "======== cdevdemo_init 3");
cdevdemo_setup_cdev(cdevdemo_devp, 0);
/*下面兩行是創建了一個總線類型,會在/sys/class下生成cdevdemo目錄
這裏的還有一個主要作用是執行device_create後會在/dev/下自動生成
cdevdemo設備節點。而如果不調用此函數,如果想通過設備節點訪問設備
需要手動mknod來創建設備節點後再訪問。*/
cdevdemo_class = class_create(THIS_MODULE, "cdevdemo");
device_create(cdevdemo_class, NULL, MKDEV(cdevdemo_major, 0), NULL, "cdevdemo");
printk(KERN_NOTICE "======== cdevdemo_init 4");
return 0;
fail_malloc:
unregister_chrdev_region(devno,1);
}
void cdevdemo_exit(void) /*模塊卸載*/
{
printk(KERN_NOTICE "End cdevdemo");
cdev_del(&cdevdemo_devp->cdev); /*註銷cdev*/
kfree(cdevdemo_devp); /*釋放設備結構體內存*/
unregister_chrdev_region(MKDEV(cdevdemo_major,0),1); //釋放設備號
}
MODULE_LICENSE("Dual BSD/GPL");
module_param(cdevdemo_major, int, S_IRUGO);
module_init(cdevdemo_init);
module_exit(cdevdemo_exit);