Linux 字符设备驱动--PWM的蜂鸣器

Linux 字符设备驱动学习

一、基本概念

  1. linux系统,一切皆文件,字符设备就是实现一切皆文件,用户可以通过访问文件的方式,来访问硬件/内核。

  2. 驱动:硬件 = 1:n 一个驱动程序,可以管理很多硬件
    给驱动程序 分配一个主设备号, 给该驱动管理的每个硬件 分配一个次设备号。
    同一类硬件,使用相同的驱动,所以 拥有相同的主设备号。

  3. 设备号devno,请求取 主设备major, 次设备号minor:
    major = devno >> 20;
    minor = devno<<12>>12;

  4. 已知major,minor,求 devno: devno = major <<20 | minor;

  5. 实现一个字符设备驱动
    a) 申请设备号
    b) 创建字符设备驱动对象
    c) 在用户层创建一个文件节点

在这里插入图片描述
6. 实现字符设备驱动程序

a)	向内核申请一个设备号
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
                const char *name)
dev_t *dev: 设备号,内核将 设备号放在 dev里面
                存放 第一个设备号
baseminor: 起始的次设备号,    次设备号的范围0-2^20,  baseminor可以指定从哪里开始申请
count:    你要申请多少个次设备号
name : 给设备号取个名字
返回值,  0成功   <0 失败
b)	创建一个字符设备对象,然后初始化对象的属性和方法
1) struct cdev chdevobj;      //定义
2) void cdev_init(struct cdev * cdev,struct file_operations * fops)
    等同于    cdev.ops = & fops;
3) int cdev_add(struct cdev  p[],dev_t devno,unsigned count)
    count:  这次添加 多少个 字符设备对象 到内核中
    返回值:      0-成功   <0 失败
c)	在用户层创建一个设备文件节点
        1)创建一个类class --- cls = class_create(THIS_MODULE,"chdev class");
        2)在类中,创建一个文件节点--- device_create(cls,NULL,devno,NULL,"chdev%d",0);

在这里插入图片描述
7. struct cdev 结构体

struct cdev { 
struct kobject kobj;                  //内嵌的内核对象.
struct module *owner;                 //该字符设备所在的内核模块的对象指针.
const struct file_operations *ops;    //该结构描述了字符设备所能实现的方法,是极为关键的一个结构体.
struct list_head list;                //用来将已经向内核注册的所有字符设备形成链表.
dev_t dev;                            //字符设备的设备号,由主设备号和次设备号构成.
unsigned int count;                   //隶属于同一主设备号的次设备号的个数.
};
Notes:从中可以看出设备驱动的核心:申请设备结构体资源后填充设备编号和操作集
  1. struct file 结构体
struct file {      //表示一个打开的文件
......
const struct file_operations	*f_op;	// 文件操作集
fmode_t	f_mode;				    // 文件模式,是否可读写
loff_t	f_pos;				    // 当前读/写位置
unsigned int	f_flags;			    // 文件标志,是否阻塞式操作
/***********************************************************************
私有数据 --- 内核从来不会访问 这个变量, 是给 驱动开发者使用。
##一般我们在open的时候, 将一个空间存入该地址. 在  close  read  write  ioctl的时候取出来使用,在close释放
***********************************************************************/
void	*private_data;			        // 驱动可以用作任意目的或者忽略该字段,可以指向分配的数据,但release时需要释放,一般用于保存硬件资源
......
}
Notes:是一个内核结构,在open是创建,传递给该文件上进行操作的所有函数,知道最后的close,被关闭后会释放该结构体
  1. struct inode 结构体
struct inode {      //代表一个屋物理设备节点
......
const struct inode_operations *i_op;		
dev_t	i_rdev;		// 对表示设备文件的inode结构,该字段包含了正在的设备编号
union {
struct pipe_inode_info	*i_pipe;
struct block_device	*i_bdev;
struct cdev		*i_cdev;	 // 字符设备的内核的内部结构,当inode指向一个字符设备文件时,该字段包含指向struct cdev结构的指针	
};
......
}

在这里插入图片描述
10. file_operation结构体:把系统调用和驱动程序关联起来的关键数据结构。

struct file_operations ***_ops={ 
  .owner = THIS_MODULE, 
  .llseek = ***_llseek, 
  .read = ***_read, 
.write = ***_write, 
.mmap = ***_mmap;
  .ioctl = ***_ioctl, 
  .open = ***_open, 
  .release = ***_release, 
 };


a)	struct module *owner :根本不是一个操作;它是一个指向拥有这个结构的模块的指针。这个成员用来在它的操作还在被使用时阻止模块被卸载。
b)	loff_t (*llseek) (struct file * filp , loff_t p, int orig):用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值。
c)	ssize_t (*read) (struct file * filp, char __user * buffer, size_t size,loff_t * p):用来从设备中获取数据。
d)	ssize_t (*write) (struct file * filp, const char __user *buffer, size_t count, loff_t * ppos):用来发送数据给设备。                     读写皆为阻塞操作
e)	int (*mmap) (struct file *, struct vm_area_struct *):用来请求将设备内存映射到进程的地址空间。
f)	int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg):ioctl系统调用提供了发出设备特定命令的方法(例如格式化软盘的一个磁道, 这不是读也不是写)。另外, 几个 ioctl 命令被内核识别而不必引用 fops 表。
g)	int (*open) (struct inode * inode , struct file * filp ):打开一个字符设备,尽管这常常是对设备文件进行的第一个操作, 不要求驱动声明一个对应的方法。int (*release) (struct inode *, struct file *):当最后一个打开设备的用户进程执行close()系统调用的时候,内核将调用驱动程序release()函数:void release(struct inode inode,struct file *file):主要任务是清理未结束的输入输出操作,释放资源,用户自定义排他标志的复位等。
  1. 8、9、10三者关系:
    a) file_operations绑定于file
    b) inode与file:
    i. inode为真实设备
    ii. file为进程相关的文件描述符,和inode为 多为一的关系
    c) 设备打开是首先由设备路径名去索引对应文件系统,找到真实的文件,并打开,这样当进程再次打开时则不用在重复该动作,直接在进程对应的file索引表就可以找到设备并返回索引号地址给用户态用。当打开文件时由inode找到真实设备,在chrdev_open中通过inode找到真实设备的i_cdev,将其指定的文件操作集分享给file用于绑定设备的操作集,并由其调用设备真实的open(用i_cdev 马上open也可以),file与进程相关,file为内核结构,最终映射为进程打开文件集的索引地址给用户态使用即FILE *fp,这样后面在用户态操作read/write等函数时由于之前已经绑定了文件真实的read/write等就可以直接使用了

二、 相关操作接口

a)	dev_t --- 设备号的数据类型,32位,前12位为主设备号,后20位位次设备号
b)	int MAJOR(dev_t dev);
    由设备号抽取主设备号
    主设备号标志设备对应的驱动程序
c)	int MINOR(devt dev);  由设备号抽取次设备号  此设备号标志设备文件所指的设备
d)	dev_t MKDEV(unsigned int major, unsigned int minor);    由主/次设备号构造dev_t

e)	int register_chrdev_region(dev_t from, unsigned count, const char *name);
作用:指定设备编号来静态注册一个字符设备
from:注册的指定其实设备编号,比如:MKDEV(100,0),表示起始设备对应的主设备号为100,次设备号为0
count:需要连续注册的次设备号的个数,比如上例次设备号的起始为0,count=100,表示0~99的次设备号都要绑定在同一个file_operations的操作方法
*name:字符设备名称,用户态访问的标志
返回值小于0表示注册失败

f)	int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name);
作用:动态分配一个字符设备,注册成功则将主次设备号放入dev
*dev:存放其实设备编号的指针,注册成功即被赋值为设备号,由MAJOR/MINOR抽离主次设备号
baseminor:起始次设备号
count:需要连续注册的次设备号数量
name:字符设备名称

g)	void cdev_init(struct cdev *cdev, const struct file_operations *fops);  作用:初始化cdev结构体,并将fops放入cdev->ops     绑定操作集到对应的设备号

h)	int cdev_add(struct cdev *p, dev_t dev, unsigned count); 作用:将cdev结构体添加到系统中,并将dev(注册号的设备)放入cdev->dev里,count(次设备号个数)放入cdev->count
 
i)	int cdev_del(struct cdev *cdev, dev_t dev, unsigned count); 作用:将系统中的cdev结构体删除   卸载

j)	void unregister_chrdev_region(dev_t from, unsigned count);
    作用:注销字符设备
from:注销指定起始设备号
count:需要连续注销的次设备号个数,与注册对应

三、一个简单的实例代码:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
/*
    如何实现 字符设备驱动程序
    1.向内核 申请一个 设备号
    int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
                const char *name)
    dev_t *dev: 设备号,内核将 设备号放在 dev里面
                存放 第一个设备号
    baseminor: 起始的次设备号,    次设备号的范围0-2^20,  baseminor可以指定从哪里开始申请
    count:    你要申请多少个次设备号
    name : 给设备号取个名字
    返回值,  0成功   <0 失败
    2.创建一个 字符设备 对象,然后初始化对象的属性和方法
        1)  struct cdev chdevobj;
        2)  void cdev_init(struct cdev * cdev,struct file_operations * fops)
              等同于    chdevobj.ops = &chdev_ops;
        3)  int cdev_add(struct cdev  p[],dev_t devno,unsigned count)
              count:  这次添加 多少个 字符设备对象 到内核中
              返回值:      0-成功   <0 失败

    3.在用户层创建一个设备文件节点
        1)创建一个类class
        cls = class_create(THIS_MODULE,"chdev class");
        2)在类 中,创建一个 文件节点
        device_create(cls,NULL,devno,NULL,"chdev%d",0);
*/
int chdev_open(struct inode *inode, struct file *file){
    printk("%s->%d\n",__FUNCTION__,__LINE__);
    return 0;
}
int chdev_close (struct inode *inode, struct file *file){
    printk("%s->%d\n",__FUNCTION__,__LINE__);
    return 0;
}
struct file_operations chdev_ops = {
    .open = chdev_open,
    .release = chdev_close,
};
/*字符设备驱动对象*/
struct cdev chdevobj;
struct class *cls;

int module_fun_init(void){
    dev_t devno;
    int major,minor;

    /*  1.向内核申请设备号*/
    if(alloc_chrdev_region(&devno,0,1,"chdev test") <0){
        printk("alloc_chrdev_region err %s->%d\n",__FUNCTION__,__LINE__);
        return -12;
}

	/*操作设备号*/
    major =  MAJOR(devno) ;              //major = devno >> 20;
    minor =  MINOR(devno);                //devno <<12>>12;
    devno =  MKDEV(major,minor);             //major<<20 | minor;

    chdevobj.dev = devno;

    printk("%s->%d get major%d  minor%d\n",__FUNCTION__,__LINE__,major,minor);

    /*初始化字符设备驱动对象*/
    cdev_init(&chdevobj,&chdev_ops);
    if(cdev_add(&chdevobj,devno,1) <0){
        printk("cdev_add err %s->%d\n",__FUNCTION__,__LINE__);
        return -14;
    }

    /*第三步, 1.创建一个类*/
    cls = class_create(THIS_MODULE,"chdev class");
    if(!cls){
        printk("class_create err %s->%d\n",__FUNCTION__,__LINE__);
        return -16;
    }

/*创建一个节点/文件,在类下
在 /dev/目录下,会创建  /dev/chdev0 文件节点 */
    device_create(cls,NULL,devno,NULL,"chdev%d",0);
    return 0;
}

void  module_fun_exit(void){
    cdev_del(&chdevobj);	 //注销字符设备
    unregister_chrdev_region(chdevobj.dev, 1); 	// 释放原先申请的设备号,设备号,释放个数
    printk("%s->%d\n",__FUNCTION__,__LINE__);
}

module_init(module_fun_init);
module_exit(module_fun_exit);

MODULE_AUTHOR("YinFei.Hu <hu_yin_fei@163.com>");
MODULE_DESCRIPTION("Simple character device driver");
MODULE_LICENSE("GPL");

展开阅读全文

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

©️2019 CSDN 皮肤主题: 点我我会动 设计师: 上身试试
应支付0元
点击重新获取
扫码支付

支付成功即可阅读