Linux字符设备驱动——Linux2.6标准字符设备驱动模型

本文详细介绍了Linux2.6标准字符设备驱动模型,包括设备特征、定义、结构体、头文件、设备号、核心结构初始化、注册与注销函数等。还概述了编写字符设备驱动的步骤,强调了设备号的动态与静态注册,以及如何处理错误和注销设备。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Linux2.6标准字符设备特征

  1. 使用一个核心结构体把需要的信息进行封装struct cdev;
  2. 安装驱动后,不会在/dev/目录下创建设备节点,需要使用mknod创建;
  3. 主设备号可以被注册多次,每次注册只会占用指定数量的次设备;
  4. 设备号使用要先使用专门的函数申请,分为静态和动态.

动态:不知道哪个号可以用,由内核分配
静态:用用户指定的号进行申请

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<linux/cdev.h>

设备号

使用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<linux/device.h>

创建设备类步骤:
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]#
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值