字符驱动之:基本框架

1. 字符设备的基本的框架:


1.1 组合主设备号和次设备号:

设备号是一个 dev_t  的一个数,其实是一个32位的数
int hello_major = 250;
int hello_minor = 0;
dev_t dev = 0;
dev = MKDEV(hello_major, hello_minor);

1.2 注册设备号:

注册上设备号后能用 lsmod 查看模块,一般是 xxx.ko 的 xxx
int result;
int number_of_devices = 1;
result = register_chrdev_region(dev, number_of_devices, "hello_dev");
if(result < 0){
    printk(KERN_WARING "hello: can't get major number %d\n", hello_major);
    return result;
}

1.3 取消注册的设备号:

dev_t devono = MKDEV(hello_major, hello_minor);
unregister_chrdev_region(devno, number_of_devices);

2. 函数说明:

MKDEV: --组合主设备号和次设备号,设备号的释放要晚于释放 cdev
    包含的库:
        linux/fs.h
    函数原型:(所在文件include/linux/kdev_t.h)
        #define MINORBITS 20
        #define MKDEV(ma.mi)    (((ma) << MINORBITS) | (mi))
    参数:
        ma,主设备号前 12 位
        mi,次设备号后 20 位
    返回值:
       设备号,类型是 dev_t ,一般的变量时 dev

register_chrdev_region: --静态注册设备号
    包含的库:
        linux/fs.h
    函数原型:
        int register_chrdev_region(dev_t from, unsigned count, const char *name)
    参数:
        from,设备号,240-254 是留给本地和实验用
        count,数量
        name,设备的名字,如果数量是多个,那名字应该放在一个数组里,这里就是是实现的(com1,com2,com3...)
            会显示在/proc/devices 中
    返回值:
        0,成功
        <0,出错

unregister_chrdev_region: --取消注册的设备号
    包含的库:
        linux/fs.h
    函数原型:
        void unregister_chrdev_region(dev_t from, unsigned count)
    参数:
        from,设备号
        count,数量

3. 两个结构体:

file_operations: --字符设备操作的集合,包括很多函数的索引


    定义: struct file_operations  hello_fops ={
          .owner=THIS_MODULE,
        ...
     }
cdev: -- 字符设备结构体
    包含的库:
        linux/cdev.h
cdev结构体是这样被定义的:

也就是这种结构:

     dev_t 本质是一个32位的整数

cdev结构体的使用:
      ①定义
        struct cdev *cdev;
      ②初始化
          cdev_init: --初始化一个 cdev 结构体,将file_operations 里的函数放到 cdev 中去
        函数原型:
          void cdev_init(struct cdev *cdev, const struct file_operations *fops)
        参数:
            cdev,要初始化的字符设备结构体
            fops,字符设备的操作集合
        例子:    
            cdev_init(&cdev,hello_fops);
            cdev.owner =THIS_MODULE;
 
      ③注册cdev
          cdev_add: --添加一个字符设备到系统
        函数原型:
          int cdev_add(struct cdev *p, dev_t dev, unsigned count)
        参数:
            p,设备的cdev结构体
            dev,传进来的cdev结构体的设备号
            count,相应设备的连续的次设备号的个数,
        返回值:
            <0,失败
            其他,成功
      例子:加载函数
            int ret = 0;
            ret =cdev_add(&cdev,devno,1);    //devno 是设备号,250那个
            if(ret <0 )
            {
                printk("cdev_add error\n");
                return ret;
            }
    ④释放 cdev 结构体
        cdev_del :从系统中移除一个cdev
        函数原型:
          void cdev_del(struct cdev *p)
        参数:
            p,要移除的 cdev 结构体
        例子:卸载函数
         cdev_del(&cdev)

4. 动态分配设备号和 cdev 结构体:

alloc_chrdev_region: --动态分配设备号
    函数原型:
          int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
           const char *name)
    参数:
        dev,存放申请好后的设备号
        baseminor,次设备号从哪开始
        count,申请的数量
        name,要申请分配的名字,是设备号的助记符,在   /proc/devices  中能查看到的
    返回值:
        0,成功
        <0,失败
    例子:
        int error;
        dev_t devno ;
        error = alloc_chrdev_region(&devno,0,1,"char_dev");
        if(error<0 )
        {
            printK("alloc_chrdev_region error\n");
            return error;
        }
      
cdev_alloc: --动态分配设备结构体 cdev
    函数原型:
          struct cdev *cdev_alloc(void)
    返回值:
        返回一个cdev的结构体
    例子:
        struct cdev *cdevp = NULL;
        在加载函数内部:
        cdevp = cdev_alloc();

5. 代码示例:

char_open.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>

MODULE_LICENSE ("GPL");

int hello_major = 250;
int hello_minor = 0;
int number_of_devices = 1;

struct cdev *cdev;

static int hello_open (struct inode *inode, struct file *file)
{
    printk (KERN_INFO "Hey! device opened\n");
    return 0;

}

static int hello_release (struct inode *inode, struct file *file)
{
    printk (KERN_INFO "Hmmm! device closed\n");
    return 0;

}

struct file_operations hello_fops = {
    .owner = THIS_MODULE,
    .open  = hello_open,
    .release = hello_release

};

static int __init hello_2_init (void)
{
    int result;

    dev_t devno = MKDEV (hello_major, hello_minor);  /* 生成设备号 */
    /* 静态注册设备号,名字显示在 /proc/devices 中 */
    result = register_chrdev_region (devno, number_of_devices, "xxhello");  
    if (result < 0) {
        printk (KERN_WARNING "hello: can't get major number %d\n", hello_major);
        return result;
    }

    /* 动态分配设备机构体cdev,得到一个设备机构体cdev */
    cdev = cdev_alloc();
    /* 将cdev中的file_operation的指针指向用户提供的结构体 */
    cdev_init (cdev, &hello_fops);
    cdev->owner = THIS_MODULE;
    /* 将cdev中的设备号赋值,并将cdev添加到系统中去,即能够用lsmod查看到 */
    result = cdev_add (cdev, devno, 1);
    if (result)
        printk (KERN_INFO "Error %d adding char_device", result);

    printk (KERN_INFO "Char device registered ...\n");
    return 0;
}

static void __exit hello_2_exit (void)
{
    dev_t devno = MKDEV (hello_major, hello_minor);

    if (cdev)
        cdev_del (cdev);

    unregister_chrdev_region (devno, number_of_devices);

    printk (KERN_INFO "char device cleaned up :)\n");
}

module_init (hello_2_init);
module_exit (hello_2_exit);
【1】对字符设备的注册,即hello_2_init做的事情其实很简单,就做了两步:1. 将设备号注册到系统中,2. 将结构体跟设备号关联并添加到系统中去

Makefile 

ifeq ($(KERNELRELEASE),)

KERNELDIR ?= /lib/modules/$(shell uname -r)/build 
#KERNELDIR ?= ~/wor_lip/linux-3.4.112
PWD := $(shell pwd)

modules:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

modules_install:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install

clean:
	rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions modules* Module*

.PHONY: modules modules_install clean

else
	obj-m := char_open.o
endif

文件的测试方法是两个文件放到一个文件夹下,make modules 或者直接 make ,然后 sudo insmod char_open.ko ,之后lsmod | grep char_open 即能看到这个模块成功的加载到了内核。

当我们要查看 printk 打印的信息时,需要用 dmesg 命令,还要说的一点是sudo dmesg -c是清除之前的显示。


6. 补充:

上边有将主设备号和次设备号组合的函数 MKDEV(major,minor),下边是从设备号中提取主设备号和次设备号:

MAJOR函数: --提取主设备号

例子:MAJOR(dev_t)

MINOR函数: --提取次设备号

例子:MINOR(dev_t)


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值