linux驱动程序注册

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qulang000/article/details/83479872

目录

(1)申请设备号:

  (2)   注册设备

(3)创建设备节点

(4)module加载函数。

(5)module卸载函数。

(6)module许可声明(必须)

(7)模块参数

(8)模块依赖

(9)模块作者等信息声明


一个linux内核模块主要由如下几个部分组成:

初始化 

1、动态申请设备号 alloc_register_region 

2、初始化字符设备块 cdev_init

 3、向内核添加设备块 cdev_add

 4、创建设备类型 class_create 

5、创建设备节点 device_create 卸载 

 


卸载 

1、释放内核内存空间 kfree 
2、销毁设备节点 device_destroy 
3、销毁设备类型 class_destroy 
4、注销设备号 unregister_chrdev_region

 

(1)申请设备号:

内核中所有已分配的字符设备编号都记录在一个名为 chrdevs 散列表里。该散列表中的每一个元素是一个 char_device_struct 结构,它的定义如下:


   static struct char_device_struct {
       struct char_device_struct *next;    // 指向散列冲突链表中的下一个元素的指针
       unsigned int major;                 // 主设备号
       unsigned int baseminor;             // 起始次设备号
       int minorct;                        // 设备编号的范围大小
       char name[64];                      // 处理该设备编号范围内的设备驱动的名称
       struct file_operations *fops;       // 没有使用
       struct cdev *cdev;                  // 指向字符设备驱动程序描述符的指针
   } *chrdevs[CHRDEV_MAJOR_HASH_SIZE];

设备号为设备驱动模块程序在Linux系统中唯一识别号。其为32bits的无符号整数, 一个设备号分成主设备号和次设备号两部分:
                   主号12bits 次号20bits 。
一般情况下主设备号为一种设备类型,次设备号为这类设备的具体一个设备。 

MKDEV(ma,mi);将ma主设备号和次设备号合成一个32bit的完整设备号; 
MAJOR(dev);从一个设备号中提取出主设备号; 
MINOR(dev);从一个设备号中提取次设备号;

 分配方式有两种: 动态申请与静态申请

         2.6以前使用:

int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
/*
major 是感兴趣的主编号,
name 是驱动的名子(出现在 /proc/devices), 
fops 是缺省的 file_operations 结构. 一个对 register_chrdev 的调用为给定的主编号注册 0 - 255 的次编号, 并且为每一个建立一个缺省的 cdev 结构. 使用这个接口的驱动必须准备好处理对所有 256 个次编号的 open 调用( 不管它们是否对应真实设备 ), 它们不能使用大于 255 的主或次编号.
*/
两种申请方式都可以

 2.6以后使用: 

//动态分配设备号函数: 
int alloc_register_region(dev_t *dev, unsigned basemonor, unsigned count, const char *name); 
/*
返回值:0表示成功,非0表示失败。 
输入参数: 
dev,存放获得的设备号,指针方式传入; 
baseminor,第一个次设备号,; 
count,总共次设备号数量; 
name,被分配的设备或驱动名称。
*/
//静态分配设备号
int register_chrdev_region(dev_t first, unsigned int count, 
                char *name);
/*
from :要分配的设备编号范围的初始值(次设备号常设为0);
Count:连续编号范围.
name:编号相关联的设备名称. (/proc/devices);
*/
//释放:

Void unregist_chrdev_region(dev_t first,unsigned int count);

(2)注册设备

第一步:

内核在内部使用类型 struct cdev 的结构来描述字符设备。

struct cdev {
        struct kobject kobj;
        struct module *owner;   //所属模块
        const struct file_operations *ops;   
                //文件操作结构,在写驱动时,其结构体内的大部分函数要被实现
        struct list_head list;
        dev_t dev;          //设备号,int 类型,高12位为主设备号,低20位为次设备号
        unsigned int count;
};

有 2 种方法来分配和初始化cdev 结构:

1. 静态内存定义初始化:: 

void cdev_init(struct cdev *cdev, struct file_operations *fops);
//代码
struct cdev my_cdev;
cdev_init(&my_cdev, &fops);
my_cdev.owner = THIS_MODULE;

2.动态内存定义初始化:

struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &fops;
my_cdev->owner = THIS_MODULE;

cdev_alloc函数中没有对struct cdev的ops域进行初始化,需要在cdev_alloc函数调用之后有专门的代码对struct cdev的ops域进行初始化。

而cdev_init函数中使用通过参数传进来的struct file_operations结构体指针对struct cdev的ops域进行初始化,所以在函数cdev_init调用之后不需要再对struct cdev的ops域进行初始化。

第二步:

函数cdev_alloc和cdev_init只是(申请)并初始化了(部分)结构体struct cdev,此时,struct cdev和内核还没有任何关系。

函数cdev_add就是struct cdev结构体注册到内核中,自此内核就可以访问设备了。注册设备,通常发生在驱动模块的加载函数中。
 

int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
/*
dev 是 cdev 结构, 
num是这个设备响应的第一个设备号,
count 是应当关联到设备的设备号的数目. 常常 count 是 1, 但是有多个设备号对应于一个特定的设备的情形.
通过num和count传递了设备号
通过cdev内的ops传递了文件操作的函数指针
*/
void cdev_del(struct cdev *dev);

(3)创建设备节点

1.创建设备类型目录及相应配置:

struct class *class_create(owner, name); 
/*
返回值:为struct class指针,可用IS_ERR()函数来判断返回是否成功。 
输入参数: 
owner ,一般使用THIS_MODULE参数; 
name,设备类型名称。 
会在/sys/class中创建name参数的目录及一些文件。
*/
void class_destroy(struct class *cls); 
/*
返回值:void 
输入参数: 
cls,要删除的设备类型。
*/

2.自动创建设备节点:

struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, …); 
/*
返回值:struct device *指针,可用IS_ERR()函数来判断返回是否成功。 
class,创建额逻辑设备相关的逻辑类,为用class_create()得到的指针; 
parent,指向父级设备,一般可以为NULL; 
devt,设备号; 
drvdata,为回调函数,或携带数据,一般为NULL; 
fmt,辑设备的设备名,即在目录 /sys/devices/virtual创建的逻辑设备目录的目录名。 
自动会在/dev目录下创建fmt设备节点。
*/
void device_destroy(struct class *class, dev_t devt); 
/*
返回参数:空。 
输入参数: 
class,要删除设备节点所属的设备类型; 
devt,要删除的设备号。
*/

函数device_create()用于动态的建立逻辑设备,并对新的逻辑设备类进行相应初始化,将其与函数的第一个参数所代表的逻辑类关联起来,然后将此逻辑设备加到linux内核系统的设备驱动程序模型中。函数能够自动在/sys/devices/virtual目录下创建新的逻辑设备目录,在/dev目录下创建于逻辑类对应的设备文件

(4)module加载函数。

当通过insmod或modprobe命令加载内核module时,module的加载函数会自动被内核运行,完成本module的相关初始化工作。

module加载函数通过module_init()函数向内核注册。

(5)module卸载函数。

rmmod命令卸载某个模块时,模块的卸载函数会自动被内核执行,完成本模块初始化的相反功能。

module卸载函数通过module_exit()函数向内核注册。

6)module许可声明(必须)

许可证license声明描述内核模块的许可权限,如果不声明license,模块被加载时,将,收到内核被污染(kernel tainted)的警告。linux中可接受的license包括“GPL”,“GPL v2”,“Dual BSD/GPL”,“Dual MPL/GPL”等。

多数情况下,内核模块应遵循GPL兼容许可权,2.6内核模块最常见的是以MODULE_LICENSE("Dual BSD/GPL")语句声明模块采用BSD/GPL 双LICENSE。

(7)模块参数

(8)模块依赖

(9)模块作者等信息声明

            如MODULE_AUTHOR(),MODULE_DESCRIPTION(),MODULE_ALIAS()等。

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h> 
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>
dev_t devs;//主设备号
struct cdev cdevs;//cdev结构体

static struct class *gpio_class;
static struct device *gpio_class_dev;

#define DEV_NAME "gpios"
char buffe[100];

static int gpio_open(struct inode *inode,struct file *file)
{
  printk("gpio open success!!!!!!!!!!!!\n");
  
  return 0;
}
static ssize_t gpio_write(struct file *file ,const char __user *buf ,size_t count ,loff_t *f_pos)
{
  int gpios,i;
  get_user(gpios,(int *)buf);
  if(gpio_request(gpios,NULL) != 0)
    {
        printk("gpio request error!\n");
        return -1;
    }
  gpio_direction_output(gpios,0);
  for(i=0;i<30;i++)
    {
      mdelay(100);
      gpio_set_value(gpios, 1); 
      mdelay(100);
      gpio_set_value(gpios, 0); 
    }
  gpio_free(gpios);
  printk("write success:\t%d\n",gpios);
  
  return 0;
}

static ssize_t gpio_read(struct file *file , char __user *buf ,size_t count ,loff_t *f_pos)
{
  //put_user(buffe,buf);
  //copy_to_user((char *)buf,buffe,count);
  //printk("write success:\t%s\n",buffe);
  return 0;
}
static int gpio_ioctl(struct file *file , unsigned int cmd, unsigned long arg)
{
  int gpios = (int) cmd;
  int val   = (int) arg;
  printk("input:%d  %d\n ",gpios,val);

  if(gpio_request(gpios,NULL) != 0)
    {
        printk("gpio request error!\n");
        return -1;
    }
  gpio_direction_output(gpios,0);
  gpio_set_value(gpios, val); 
  mdelay(2000);   
  gpio_free(gpios);
  printk("write success:\t%d\n",gpios);
  
  return 0;
}
static struct file_operations gpio_fops={
	.owner = THIS_MODULE,
	.open  = gpio_open,
	.write = gpio_write,
  .read  = gpio_read,
  .unlocked_ioctl = gpio_ioctl,
};
static int __init gpio_init(void)
{
    printk("init begin---------------------------------------------------\n");
    int rst=alloc_chrdev_region(&devs,0,1,DEV_NAME);//分配主设备号
    if(rst<0)
    {
      printk("devs apply failed!!!\n");
      printk(KERN_INFO "Fail:alloc_chrdev_region()\n");
      return 0;
    }
    else printk("devs apply success%d--->%d\n",MAJOR(devs),MINOR(devs));
    //注册字符设备驱动
    cdev_init(&cdevs,&gpio_fops);
    cdevs.owner = THIS_MODULE;
    rst=cdev_add(&cdevs,devs,1);
    if(rst<0)
    {
      printk("cdev_add failed\n");
      printk(KERN_INFO "Fail:cdev_add()\n");
      return 0;
    }
    //添加设备类
    gpio_class=class_create(THIS_MODULE,DEV_NAME);//创建一个类
  	if(IS_ERR(gpio_class))
  		return PTR_ERR(gpio_class);
	  gpio_class_dev=device_create(gpio_class,NULL,devs,NULL,DEV_NAME);//在dev目录下创建相应节点
  	if(unlikely(IS_ERR(gpio_class_dev)))
  		return PTR_ERR(gpio_class_dev);

    printk("init seccss---------------------------------------------------\n");
}

static void __exit gpio_exit(void)
{
    device_destroy(gpio_class,devs);//用于从linux内核系统设备驱动程序模型中移除一个设备,并删除/sys/devices/virtual目录下对应的设备目录及/dev/目录下对应的设备文件
    class_destroy(gpio_class);
    printk("exit begin---------------------------------------------------\n");
    cdev_del(&cdevs);//注销字符设备驱动
    unregister_chrdev_region(devs,1);//注销申请的主次设备号
    printk("exit success---------------------------------------------------\n");
    
    
}

module_init(gpio_init);
module_exit(gpio_exit);

MODULE_AUTHOR("xxx123456");
MODULE_DESCRIPTION("gpio driver");
MODULE_LICENSE("GPL");

 

展开阅读全文

没有更多推荐了,返回首页