【驱动】——字符设备驱动程序

字符设备不得不说的那些事:

一: 设备号:主设备号,次设备号:

  数据类型 dev_t(unsigned int) 定义设备号  高12位主设备号 低20位次设备号;

二: 设备号的作用:

  应用程序通过主设备号找到驱动程序;

三:如何分配设备号:

  ①:静态分配:

    1: cat /proc/devices 查看linux系统哪个设备号没有被占用;

    2: dev_t dev_id = MKDEV(主设备号,次设备号)  根据你的设备个数分配次设备号 如果设备个数只有一个,一般此设备号从0开始;

    3: 调用 register_chrdev_region(dev_t from,unsigned count,const char *name);

      from:待申请的设备号     count:待申请的设备号数目   name:设备名称(出现在 /proc/devices)  

  ②:动态分配:

    1:调用 alloc_chrdev_region 直接向内核去申请设备号,也就是让操作系统内核帮你分配设备号。

  ③:设备号的注销:

    1:void unregister_chrdev_region(dev_t from, unsigned count);

四:重要的数据结构:

  ①:文件结构: struct file; //描述打开文件后的状态

    生命周期:当打开文件后,内核自动创建一个结构体 struct file  文件关闭之后 struct file 被自动销毁。

    重要成员:

      struct file_operations *f_op;   //指向驱动程序中实现的各个硬件操作方法;

      unsigned int f_flags;   //文件操作属性;

      loff_t f_ops;   //文件操作位置;

      void *private_data; //存放私有数据

  ②:inode 结构: struct inode; //用于记录文件物理信息

    生命周期:文件存在,内核创建;文件销毁,内核销毁对应的inode;

    重要成员:

      dev_t i_rdev; //存放设备号

      struct cdev *i_cdev;    //指向一个字符设备

    注:一个文件只有一个inode,可以有多个file;

  ③:文件操作结构: struct file_operations; //一个函数指针的集合,这些函数定义了能够对设备进行的操作 在 <linux/fs.h> 中定义

  ④:字符设备 struct cdev

五:重要的函数:

  ①:分配设备号,见【三:如何分配设备号】

  ②:注册字符设备:

    1: void cdev_init(struct cdev *cdev, struct file_operations *fops);  //将字符设备与处理函数进行绑定,把 struct file_operations 注册到内核中

    2: int cdev_add(struct cdev *dev, dev_t num, unsigned int count);  //将 dev 添加到内核的cdev的数组之中 下标是以设备号为索引!一旦完成对 cdev的注册,就等于有了一个真实的字符设备,关键这个驱动就有了,对应的操作集合 fops;

    3: void cdev_del(struct cdev *dev);    //从系统中移除一个字符设备

六:简单的设备驱动程序例子:

Makefile:

ifeq ($(KERNELRELEASE), )
    KERNELDIR ?= /lib/modules/$(shell uname -r)/build   
    PWD := $(shell pwd)
all: clean
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
    -rm -rf *.o *~ core .depend .*.cmd *.mod.c .tmp_versions Module.* Makefile.xen modules.order
clean:
    -rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions Module.* Makefile.xen modules.order
else
MODULE_NAME=scull
    obj-m := $(MODULE_NAME).o
    $(MODULE_NAME)-objs := file_op.o my_project.o
endif

my_project.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kernel.h>
#include <asm/uaccess.h>

#include "file_op.h"

uint dev_major = 60; 
char device_name[20] = "ngnetboy";
dev_t scull_dev;    //设备号
static struct cdev scull_cdev;  //字符设备

struct file_operations scull_fops = { 
    .owner = THIS_MODULE,
    .read = scull_read,
    .write = scull_write,
    .ioctl = scull_ioctl,
    .open = scull_open,
    .release = scull_release,
};
static int __init scull_init(void){
    int ret;

    printk("hello kernel!
");
    // make device num
    scull_dev = MKDEV(dev_major, 0);
    // register device 
    ret = register_chrdev_region(scull_dev, 1, device_name);
    if (ret < 0){
        printk("register chrdev region failed!
");
        ret = alloc_chrdev_region(&scull_dev, 0, 1, device_name);
        dev_major = MAJOR(scull_dev);
    }
    if (ret < 0){
        printk("%s register failed!
", device_name);
        return ret;
    }
    //scull_cdev = cdev_alloc();
    cdev_init(&scull_cdev, &scull_fops);
    ret = cdev_add(&scull_cdev, scull_dev, 1);
    if (ret < 0){
        printk("cdev add failed!
");
        goto chr_quit1;
    }
    return 0;
chr_quit1:
    unregister_chrdev_region(scull_dev, 1);
    return ret;
}

static void __exit scull_exit(void){

    cdev_del(&scull_cdev);
    unregister_chrdev_region(scull_dev, 1);

    printk("bye kernel!
");
}


module_init(scull_init);
module_exit(scull_exit);

MODULE_AUTHOR("ngnetboy");
MODULE_DESCRIPTION("a simple character utility for loading localities");
MODULE_LICENSE("GPL");
MODULE_VERSION("0.0.0.3");

file_op.c

 1 #include <linux/fs.h>
 2 #include <asm/uaccess.h>
 3 #include <linux/init.h>
 4 #include <linux/module.h>
 5 #include <linux/types.h>
 6 #include <linux/cdev.h>
 7 #include <linux/kernel.h>
 8 
 9 #include "file_op.h"
10 
11 int scull_read(struct file *filep, char __user *buff, size_t count, loff_t *offp){
12     char val[20] = "this is read!";
13     int ret;
14 
15     ret = copy_to_user(buff, val, count);
16     return 0;
17 }
18 int scull_write(struct file *filep, const char __user *buff, size_t count, loff_t *offp){
19     char val[20];
20     int ret;
21 
22     printk("hello write!
");
23     ret = copy_from_user(val, buff, count);
24     printk("ret :%d copy from user %s
", ret, val);
25     return 0;
26 }
27 int scull_open(struct inode *inode, struct file *filp){
28     printk("hello open!
");
29     return 0;
30 }
31 int scull_release(struct inode *inode, struct file *filp){
32     printk("hello release!
");
33     return 0;
34 }
35 int scull_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg){
36 
37     return 0;
38 }

使用 mknod /dev/netboy c 60 0 即可在 /dev 下创建一个可供应用程序可用的字符设备文件结点;

七:当应用程序 open 创建的字符设备时内核和驱动都做了那些事情?

  1:当应用程序调用 open 打开设备文件(打开设备);

  2:调用 C 库的open实现,会保存 open 的系统调用号;

  3:C库的open实现调用 swi 触发一个软中断,跳转到内核态;

  4:根据系统调用号,在系统调用表中找到open对应内核系统调用实现 sys_open;

  5:调用 sys_open, sys_open 会做如下工作:

    ①:通过 inode.i_rdev 获取设备号;

    ②:根据设备号在内核cdev数组中找到对应的字符设备驱动;

    ③:然后将找到的cdev的地址赋值给 inode.i_cdev 用于缓存和别的用途;

    ④:创建 struct file 结构体内存 用于描述打开的设备文件信息;

    ⑤:根据已经获得的 cdev,从而获得其中的驱动操作集合ops();

    ⑥:将字符设备驱动的操作接口ops再赋值给 file->ops;

    ⑦:最后再调用一次 file->f_op->open = led_open;

八:四个重要的数据结构之间有什么联系?

  1:struct file_operations fops ;是由驱动开发者实现,包括相应的函数实现;并且由驱动开发者把fops注册到内核中与struct cdev 绑定到一起;

  2:使用 mknod 创建 /dev/netboy 时,struct inode node,便已被创建,并随着文件的销毁而被释放;

  3:当驱动程序被调用,用户程序调用open打开字符设备的时候,struct file 便被创建,内核通过设备号找到对应的字符设备【struct cdev】,然后把fops赋值给 struct file;

    

原文地址:https://www.cnblogs.com/ngnetboy/p/4605875.html