Linux字符设备驱动——Linux2.6标准字符设备驱动模型
文章目录
- Linux2.6标准字符设备特征
- Linux2.6标准字符设备定义
- 字符设备驱动结构体
- 头文件
- 设备号
- 分配核心结构函数
- 静态设备号注册函数
- 动态设备号注册函数
- 设备号注销函数(释放设备号函数)
- 核心结构初始化函数
- 注册函数
- 注销函数
- 编写Linux2.6字符设备模型步骤
- 第一步:变量定义
- 第二步到第八步在入口函数完成
- 第二步:分配一个核心结构
- 第三步:申请设备号
- 第四步:初始化核心结构
- 第五步:注册核心结构
- 第六步和第七步增加自动创建设备文件功能
- 第六步:创建一个设备类
- 第七步:创建一个设备,报告设备信息
- 第八步:出错处理
- 第九步:注销核心结构,释放设备号
- Linux2.6标准字符设备驱动模型源码
- 驱动程序zx_linux26_led.c源代码
- 应用程序app.c源代码
- 示例代码测试
Linux2.6标准字符设备特征
- 使用一个核心结构体把需要的信息进行封装struct cdev;
- 安装驱动后,不会在/dev/目录下创建设备节点,需要使用mknod创建;
- 主设备号可以被注册多次,每次注册只会占用指定数量的次设备;
- 设备号使用要先使用专门的函数申请,分为静态和动态.
动态:不知道哪个号可以用,由内核分配
静态:用用户指定的号进行申请
Linux2.6标准字符设备定义
字符设备驱动结构体
struct cdev
{
struct kobject kobj;
struct module *owner;
const struct file_operations *ops; //设备文件操作方法
struct list_head list;
dev_t dev; //32位设备号,包含主次
unsigned int count; //需要占用的连续次设备号的个数
};
ops:文件操作集合指针
dev:起始设备号,(包含主次设备号,dev_t实际上是一个u32类型)
头文件
#include
设备号
使用32位数据类型表示,其中12位表示主设备号,低20位表示次设备号。
主:0~4096-1
次:0~1M-1
专门定义了dev_t类型来存放设备号类型,实际就是unsigned long类型
由于主次设备号都在一个数据中,内核提供了一些宏来合成设备号和分离设备号
MKDEV(ma,mi) ma主设备号,mi此设备号,这个宏是合成设备号
MAJOR(devnr) 从设备号devnr中得到主设备号
MANOR(devor) 从设备号devnr中得到次设备号
分配核心结构函数
struct cdev *cdev_alloc(void)
功能:在堆区空间中分配一个核心结构cdev,注意,不使用时候要使用 kfree 函数释放。
返回值:返回分配到struct cdev结构空间首地址。
注意:说明:用完记得释放,否则会造成内存泄漏。
如果在静态存储区中定义全局变量,可以不适用这个函数,需要输入以下代码:
struct cdev dev;
dev.count = 1;
如果想在堆空间中分配 cdev结构空间,就使用这个函数。出于内存利用率考虑建议使用这个函数进行分配核心结构空间。
输入以下代码实现:
struct cdev *pdev;
pdev = cdev_alloc();
pdev->count = 1;
静态设备号注册函数
int register_chrdev_region(
dev_t from //起始设备号(主、次)
unsigned count, //连续的次设备号数量
const char *name ) //设备名,不需要和/dev/的设备文件名相同
功能:注册一个设备号范围
参数:
from: 起始设备号(主、次)
count: 连续的次设备号数量
name: 设备名,不需要和/dev/的设备文件名相同,是/proc/device文件的名字
返回值: 成功;返回0,失败:返回负数
动态设备号注册函数
int alloc_chrdev_region(dev_t *dev, //存放分配到的第一个设备(包含主次)
unsigned baseminor, //要分配起始次设备号
unsigned count, // 连续的次设备号数量
const char *name) // 设备名,不需要和/dev/的设备文件名相同
功能: 注册一个设备号范围
参数:
dev :存放分配到的第一个设备(包含主次设备号)
baseminor :要分配起始次设备号
count :连续的次设备号数量
name :设备名,不需要和/dev/的设备文件名相同
返回值 :成功:返回0,失败:返回负数
设备号注销函数(释放设备号函数)
void unregister_chrdev_region( dev_t from, //起始设备号(主、次)
unsigned int count) //连续的次设备号数量
功能: 释放一个范围的设备号
参数
from :起始设备号(主、次) (包含主次设备号)
count:连续的次设备号数量
返回值:无
核心结构初始化函数
void cdev_init( struct cdev *cdev, //需要初始化的核心结构指针
const struct file_operations *fops) //文件操作集合
功能:初始化核心结构,具体做的是清0 核心结构,初始化核心结构的list,kobj,ops成员
参数:
cdev :需要初始化的核心结构指针
fops :文件操作方法结构指针
返回值 :无
注意:说明:写这种种驱动模型时候,不需要在定义struct cdev结合变量初始化,因为调用cdev_init函数时候会把它清0,定义时候的初始无效。
源代码:
void cdev_init(struct cdev *cdev, /* 需要初始化的核心结构指针*/
const struct file_operations *fops) /* 文件操作集合*/
{
memset(cdev, 0, sizeof *cdev); /* 清0 核心结构 */
INIT_LIST_HEAD(&cdev->list); /* 初始化核心结构的list,kobj,ops成员*/
kobject_init(&cdev->kobj, &ktype_cdev_default);
cdev->ops = fops; /* 填充文件操作方法成员*/
}
注册函数
int cdev_add(struct cdev *p, //已经初始化的核心结构指针
dev_t dev, //始设备号(包含主次设备号在内)
unsigned count) //连续次设备号数量
功能:注册一个cdev结构
参数:
p:已经初始化的核心结构指针
dev:起始设备号(包含主次设备号在内)
count:连续次设备号数量
返回值:成功:返回0,失败:返回负数
注销函数
void cdev_del(struct cdev *p)
功能:注销一个cdev结构
参数:
p:前面注册的struct cdev结构指针
返回值:无
编写Linux2.6字符设备模型步骤
第一步:变量定义
static char * pchrdev_name = DEV_NAME; //设备名
static struct cdev *pcdev = NULL; //分配到struct cdev结构空间首地址
static unsigned int major = 0; //主设备号,想动态设置0,否则设置正数1~255 (自己保证可用)
static unsigned int minor = 0; //次设备号
static dev_t devnr = 0; //设备号
static struct class *linux26_class = NULL; //Linux2.6标准字符设备类
static struct device *this_device = NULL; //在设备类中的设备
第二步到第八步在入口函数完成
第二步:分配一个核心结构
int ret = 0
pcdev = cdev_alloc();
if(pcdev == NULL)
{
printk(KERN_EMERG "cdev_alloc error\n");
ret = -1;
goto cdev_alloc_err; //分配一个核心结构出错
}
第三步:申请设备号
if(major) //如果用户定义了主设备号,使用静态分配的方式
{
devnr = MKDEV(major, minor); //合成设备号
ret = register_chrdev_region( devnr , 2, pchrdev_name);
if ( ret < 0 ) { //返回0表示成功
printk(KERN_EMERG "register_chrdev_region error\n");
goto register_chrdev_region_err; //静态申请设备号出错
}
}
else { //使用动态分配设备号方式
ret = alloc_chrdev_region(&devnr, minor, 2, pchrdev_name);
if ( ret < 0 ){
printk(KERN_EMERG "alloc_chrdev_region error\n");
goto alloc_chrdev_region_err; //动态申请设备号出错
}
major = MAJOR(devnr); //获得主设备号
minor = MINOR(devnr); //获得次设备号
}
第四步:初始化核心结构
cdev_init(pcdev, &chrdev_fops);
第五步:注册核心结构
ret = cdev_add(pcdev, devnr, 2);
if ( ret < 0 ) {
printk(KERN_EMERG "cdev_add error\n");
goto cdev_add_err; //注册核心结构出错
}
第六步和第七步增加自动创建设备文件功能
第六步:创建一个设备类
Linux2.6内核支持一个udev的程序,这个程序是一个运行在用户空间的应用程序,这个程序会检测内存上报的热拔插的事件。
在开发板上输入以下代码:
ls /sys/class
/sys/class/类名/设备名/uevent
通过cat /sys/class/zxdev26/zxlinux26leds/uevent查看内容
包含的头文件:
#include
创建设备类步骤:
1、创建一个类
2、创建设备
3、报告事件
linux26_class = class_create(THIS_MODULE, CLASS_NAME); //取一个不同于设备名的类名
if ( IS_ERR(linux26_class) )
{
ret = PTR_ERR(linux26_class);
goto class_create_err; //创建一个设备类出错
}
第七步:创建一个设备,报告设备信息
this_device = device_create(linux26_class,NULL ,devnr,NULL,"%s", pchrdev_name);
if ( IS_ERR(this_device) ) {
ret = PTR_ERR(this_device);
goto device_create_err; //创建一个设备出错
}
创建成功后会显示注册的信息,即报告的事件
[root@ZX20150811 /home]# ls -l /dev
crw-rw---- 1 root root 10, 130 Aug 12 2015 watchdog
crw-rw---- 1 root root 252, 0 Aug 12 2015 watchdog0
crw-rw---- 1 root root 1, 5 Aug 12 2015 zero
crw-rw---- 1 root root 150, 23 Aug 17 2015 zxlinux26leds
[root@ZX20150811 /home]# ls /sys/class/ | grep zx
zxdev26
[root@ZX20150811 /home]# cat /sys/class/zxdev26/zxlinux26leds/uevent
MAJOR=150
MINOR=23
DEVNAME=zxlinux26leds
[root@ZX20150811 /home]#
第八步:出错处理
如果其中一个流程没有完成,中间出错了,需要进行出错处理,每写一步,处理一步
device_create_err: //创建一个设备出错
class_destroy(linux26_class);
class_create_err: //创建一个设备类出错
cdev_del(pcdev);
cdev_add_err: //注册核心结构出错
unregister_chrdev_region(devnr, 2);
alloc_chrdev_region_err: //动态申请设备号出错
register_chrdev_region_err: //静态申请设备号出错
kfree(pcdev);
cdev_alloc_err: //分配一个核心结构出错
return ret ;
第九步:注销核心结构,释放设备号
(注册时顺序步骤执行,而释放是逆序执行)
device_destroy(linux26_class, devnr);
class_destroy(linux26_class);
cdev_del(pcdev);
unregister_chrdev_region(devnr, 2);
kfree(pcdev);
Linux2.6标准字符设备驱动模型源码
驱动程序zx_linux26_led.c源代码
#include <linux/module.h> //模块包含的头文件
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <asm/uaccess.h> //copy_from_user ,copy_to_user
#include<linux/gpio.h> // GPIO
#define CLASS_NAME "zxdev26"
#define DEV_NAME "zx_linux26_led"
#define LED_NUM (4)
static unsigned int major = 0; //主设备号,想动态设置0,否则设置正数1~255 (自己保证可用)
static unsigned int minor = 0; //次设备号
static dev_t devnr = 0; //设备号
static char * pchrdev_name = DEV_NAME;
static struct cdev *pcdev = NULL;
static struct class *linux26_class = NULL;
static struct device *this_device = NULL;
static unsigned int LED_pin[LED_NUM] = {
EXYNOS4X12_GPM4(0),
EXYNOS4X12_GPM4(1),
EXYNOS4X12_GPM4(2),
EXYNOS4X12_GPM4(3),
};
static ssize_t chrdev_read (struct file *pfile, char __user *buff, size_t size, loff_t *off)
{
printk(KERN_EMERG "line:%d,%s is call\n", __LINE__, __FUNCTION__);
return 0;
}
static ssize_t chrdev_write(struct file *filp,const char __user *user_buf,size_t count,loff_t *off)
{
int ret = 0;
char buf[LED_NUM] = {0};
int i = 0;
printk(KERN_EMERG "line:%d,%s is call\n", __LINE__, __FUNCTION__);
if(count == 0){
return 0;
}
if(count > LED_NUM){ //板子只有4盏灯。
count = LED_NUM;
}
ret = copy_from_user(buf, user_buf, count); //用户空间传数据到内核空间
if(ret){
return ret;
}
for(i = 0; i < count; i++)
{
if(buf[i] == 1){
gpio_set_value(LED_pin[i],0);/* 亮 */
}else if(buf[i] == 0){
gpio_set_value(LED_pin[i],1);/* 灭 */
}
}
return count;
}
static int chrdev_open(struct inode *pinode, struct file *pfile)
{
printk(KERN_EMERG "line:%d,%s is call\n", __LINE__, __FUNCTION__);
return 0;
}
static int chrdev_release(struct inode *pinode, struct file *pfile)
{
printk(KERN_EMERG "line:%d,%s is call\n", __LINE__, __FUNCTION__);
return 0;
}
static const struct file_operations chrdev_fops ={
.read = chrdev_read,
.write = chrdev_write,
.release = chrdev_release,
.open = chrdev_open,
};
static int __init zx_linux26_led_init(void)
{
int ret = 0;
int i = 0;
for(i = 0;i < LED_NUM; i++)
{
ret = gpio_request(LED_pin[i],"leds");
if(ret < 0){
printk(KERN_EMERG "gpio_request is error\n");
goto gpio_request_err;
}
gpio_direction_output(LED_pin[i],1); //四盏灯设置为输出功能,并且输出高电平
}
pcdev = cdev_alloc(); //第1步:分配核心结构
if(pcdev == NULL) {
printk(KERN_EMERG "cdev_alloc error\n");
ret = -1;
goto cdev_alloc_err;
}
if(major) { //第2步:申请设备号
devnr = MKDEV(major, minor);
ret = register_chrdev_region( devnr , 2, pchrdev_name); //使用静态分配设备号方式
if ( ret < 0 ) {
printk(KERN_EMERG "register_chrdev_region error\n");
goto register_chrdev_region_err;
}
}else {
ret = alloc_chrdev_region(&devnr, minor, 2, pchrdev_name); //使用动态分配设备号方式
if ( ret < 0 ) {
printk(KERN_EMERG "alloc_chrdev_region error\n");
goto alloc_chrdev_region_err;
}
major = MAJOR(devnr);
minor = MINOR(devnr);
}
printk(KERN_EMERG "major:%d,minor:%d\n", major, minor);
cdev_init(pcdev, &chrdev_fops); //第3步:初始化cdev结构
ret = cdev_add(pcdev, devnr, 2); //第4步:注册核对结构
if ( ret < 0 ){
printk(KERN_EMERG "cdev_add error\n");
goto cdev_add_err;
}
/* 增加自动创建设备文件功能 */
//第5步:创建一个设备类
// linux26_class = class_create(THIS_MODULE,pchrdev_name);//可以和设备名相同
linux26_class = class_create(THIS_MODULE, CLASS_NAME); //取一个不同于设备名的类名
if ( IS_ERR(linux26_class) ) {
ret = PTR_ERR(linux26_class);
goto class_create_err;
}
this_device = device_create(linux26_class,NULL,devnr,NULL,"%s", pchrdev_name); //第6步:创建一个设备,报告设备信息
if ( IS_ERR(this_device) ) {
ret = PTR_ERR(this_device);
goto device_create_err;
}
printk(KERN_EMERG "%s is OK!!!\n",__FUNCTION__);
return 0;
device_create_err: //出错处理
class_destroy(linux26_class);
class_create_err:
cdev_del(pcdev);
cdev_add_err:
unregister_chrdev_region(devnr, 2);
alloc_chrdev_region_err:
register_chrdev_region_err:
kfree(pcdev);
cdev_alloc_err:
return ret ;
gpio_request_err:
for(--i;i >=0 ; i--)
{
gpio_free(LED_pin[i]);
}
return ret ;
}
static void __exit zx_linux26_led_exit(void)
{
int i = 0;
for(i=0;i<LED_NUM;i++)
{
gpio_set_value(LED_pin[i],1); //灭灯
gpio_free(LED_pin[i]); //释放GPIO
}
device_destroy(linux26_class, devnr);
class_destroy(linux26_class);
cdev_del(pcdev);
unregister_chrdev_region(devnr, 2);
printk(KERN_EMERG "Goodbye,zx_linux26_led\n");
}
module_init(zx_linux26_led_init);
module_exit(zx_linux26_led_exit);
MODULE_LICENSE("GPL");
应用程序app.c源代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define DEV_NAME "/dev/zx_linux26_led"
#define LED_NUM (4)
int main(void)
{
char buf[] = {1, 0, 0, 0}; //1,亮,0表示灭,
int i = 0;
int fd;
//打开设备文件 O_RDWR, O_RDONLY, O_WRONLY,
fd = open(DEV_NAME, O_RDWR);
if(fd < 0){
printf("open :%s failt!\r\n", DEV_NAME);
}
while(1)
{
write(fd, buf, LED_NUM);
memset(buf, 0, LED_NUM);
buf[i++ % LED_NUM] = 1;
sleep(1);
}
return 0;
}
示例代码测试
[root@ZX20150811 /home]# ls
app zx_linux26_led.ko
[root@ZX20150811 /home]# insmod zx_linux26_led.ko
[ 6594.735000] major:249,minor:0
[ 6594.745000] zx_linux26_led_init is OK!!!
[root@ZX20150811 /home]# ./app
[ 6597.395000] line:68,chrdev_open is call
[ 6597.395000] line:42,chrdev_write is call
[ 6598.395000] line:42,chrdev_write is call
[ 6599.395000] line:42,chrdev_write is call
[ 6600.400000] line:42,chrdev_write is call
^C[ 6601.390000] line:75,chrdev_release is call
[root@ZX20150811 /home]#
还没有评论,来说两句吧...