linux驱动字符设备框架

linux字符设备驱动是必须要学会的框架。在跟着itop4412视频学习时碰到了困惑:

1.出现了Platform_device的设备驱动

2.出现msic杂项的设备驱动

以上2个驱动与我们在ldd书上看到的驱动方式很大的不同,首先了解下ldd中说的驱动流程。


以下是驱动模型步骤:

1)获取设备号

这个可以自动获取也可以通过 cat /proc/devices查看已经被注册好的主设备号。

获取设备号需要用到以下函数和宏:

/**************************************************************

* 描述:申请已知设备号(若是0会

自动从末尾处遍历找出未用设备号)

*  参数1:  设备号  

*  参数2:  连续编号范围(可理解为多个连续设备号注册)

*  参数3:设备名称 最终显示到/dev目录下

*  返回:0 - 成功 

**************************************************************/

int register_chrdev_region(dev_t,unsigned,const char *)


/**************************************************************

* 描述:动态获取设备号(与上一个函数用0传递不同,他是从其实开始遍历)

*  参数1:  获取到的设备号 (不能为空)

*  参数2:  从起始设备号

* 参数3: 从设备个数

*  参数3:设备名称

*  返回:0 - 成功 

**************************************************************/

int alloc_chrdev_region(dev_t *,unsigned,unsigned,const char *);



       #define MAJOR(dev) ((unsigned int)((dev)  >>  MINORBITS))//取高12位主设备号

#define MINOR(dev)((unsigned int)((dev)  & MINORMASK)) //提取低20位次设备号

#define MKDEV(ma,mi)(((ma) << MINORBITS) | (mi)) //对主设备号和低设备号操作合并为dev类型。

在不同的平台 unsigned int的类型不同,尽量调用该宏来处理。

2)根据设备号确定其实现

首先看下设备实现最关键的2个结构体:

1.struct cdev{

struct kobject kobj;//内嵌内核对象

struct module *owner;//该字符设备所在的内核模块(所有者)的对象指针,一般为THIS_MODULES主要用于模块计数

const struct file_operations *ops;//该结构描述了字符设备所能实现的操作集(打开、关闭、读/写、........),是极为关键的一个结构体

struct list_head list;//用来将已经向内核注册的所有字符设备形成链表

dev_t dev; //字符设备的设备号,由主设备号和次设备构成

unsigned int count;//隶属同个设备号的次设备号个数

...

};

2. linux下“一切皆文件”的实现结构体如下:

struct file_operations {
  struct module *owner;  
    /* 模块拥有者,一般为 THIS——MODULE */
  ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);  
    /* 从设备中读取数据,成功时返回读取的字节数,出错返回负值(绝对值是错误码) */
  ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);   
    /* 向设备发送数据,成功时该函数返回写入字节数。若为被实现,用户调层用write()时系统将返回 -EINVAL*/
  int (*mmap) (struct file *, struct vm_area_struct *);  
    /* 将设备内存映射内核空间进程内存中,若未实现,用户层调用 mmap()系统将返回 -ENODEV */
  long (*unlocked_ioctl)(struct file *filp, unsigned int cmd, unsigned long arg);  
    /* 提供设备相关控制命令(读写设备参数、状态,控制设备进行读写...)的实现,当调用成功时返回一个非负值 */
  int (*open) (struct inode *, struct file *);  
    /* 打开设备 */
  int (*release) (struct inode *, struct file *);  
    /* 关闭设备 */
  int (*flush) (struct file *, fl_owner_t id);  
    /* 刷新设备 */
  loff_t (*llseek) (struct file *, loff_t, int);  
    /* 用来修改文件读写位置,并将新位置返回,出错时返回一个负值 */
  int (*fasync) (int, struct file *, int);  
    /* 通知设备 FASYNC 标志发生变化 */
  unsigned int (*poll) (struct file *, struct poll_table_struct *);  
    /* POLL机制,用于询问设备是否可以被非阻塞地立即读写。当询问的条件未被触发时,用户空间进行select()和poll()系统调用将引起进程阻塞 */
  ...
};

以下的函数操作都是对上面结构体的操作:

/******************************************************

* 描述:动态申请cdev内存(设备对象)

* 返回值: 成功 cdev对象首地址

   失败 NULL

*******************************************************/

struct cdev *cdev_alloc(void);



/******************************************************

* 描述:初始化cdev的成员,并建立cdev和file_operations之间关联

* 参数1: 被初始化的cdev对象

* 参数2:字符设备操作方法集

*******************************************************/

void cdev_init(struct cdev *p,const struct file_operations *p);


/******************************************************

* 描述:注册cdev设备对象

* 参数1: 被注册的cdev对象

* 参数2:设备的第一个设备号

* 参数3:这个设备连续的次设备号数量

* 返回值: 成功 0

*******************************************************/

int cdev_add(struct cdev *p,dev_t dev,unsigned count)


/******************************************************

* 描述:将cdev对象从系统中移除(注销)

* 参数1: struct cdev *p - 要移除的cdev对象

*******************************************************/

int cdev_del(struct cdev *p)


/******************************************************

* 描述:释放cdev内存

* 参数:struct cdev *p 要移除的cdev对象

*******************************************************/

void cdev_put(struct cdev *p)

(3)创建设备节点

可以自动创建使用 device_create函数 头文件"include/linux/device.h"

函数 extern struct device *device_create(struct class *cls, struct device *parent,dev_t devt, void *drvdata,const char *fmt, ...);中的参数比较多。
参数 struct class *cls:设备所属于的类,前面创建类的返回值
参数 struct device *parent:设备的父设备,NULL
参数 dev_t devt:设备号
参数 void *drvdata:设备数据,NULL
参数 const char *fmt:设备名称
 


还有一个摧毁设备节点的函数 extern void device_destroy(struct class *cls,dev_t devt);

只有两个参数,分别是设备类和设备号。

二、驱动模型简单实例

cdev_module.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <asm/current.h>
#include <linux/sched.h>
#include <linux/device.h>

MODULE_LICENSE("GPL");
static int major = 0; 
static int minor = 0; 
const int count = 3; 
#define DEVNAME "demo" 
static struct cdev *demop = NULL; 
//打开设备 
static int demo_open(struct inode *inode, struct file *filp) 
{ 
  //get command and pid 
  printk(KERN_INFO "(%s:pid=%d), %s : %s : %d\n", current->comm, current->pid, __FILE__, __func__, __LINE__); 
  //get major and minor from inode 
  printk(KERN_INFO "(major=%d, minor=%d), %s : %s : %d\n", imajor(inode), iminor(inode), __FILE__, __func__, __LINE__); 
  return 0; 
} 
//关闭设备 
static int demo_release(struct inode *inode, struct file *filp) 
{ 
  //get command and pid 
  printk(KERN_INFO "(%s:pid=%d), %s : %s : %d\n", current->comm, current->pid, __FILE__, __func__, __LINE__); 
  //get major and minor from inode 
  printk(KERN_INFO "(major=%d, minor=%d), %s : %s : %d\n", imajor(inode), iminor(inode), __FILE__, __func__, __LINE__); 
  return 0; 
} 
//读设备
static ssize_t demo_read(struct file *filp, char __user *buf, size_t size, loff_t *offset) 
{
  struct inode *inode = filp->f_path.dentry->d_inode; 
  //get command and pid 
  printk(KERN_INFO "(%s:pid=%d), %s : %s : %d\n", current->comm, current->pid, __FILE__, __func__, __LINE__); 
  //get major and minor from inode 
  printk(KERN_INFO "(major=%d, minor=%d), %s : %s : %d\n", imajor(inode), iminor(inode), __FILE__, __func__, __LINE__); 
  return 0; 
} 
//写设备 
static ssize_t demo_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset) 
{ 
  struct inode *inode = filp->f_path.dentry->d_inode; 
  //get command and pid 
  printk(KERN_INFO "(%s:pid=%d), %s : %s : %d\n", current->comm, current->pid, __FILE__, __func__, __LINE__); 
  //get major and minor from inode 
  printk(KERN_INFO "(major=%d, minor=%d), %s : %s : %d\n", imajor(inode), iminor(inode), __FILE__, __func__, __LINE__); 
  return 0; 
} 
//操作方法集 
static struct file_operations fops = {
  .owner = THIS_MODULE, .open = demo_open, 
  .release= demo_release, 
  .read = demo_read, 
  .write = demo_write, 
}; 
//cdev设备模块初始化 
static int __init demo_init(void) 
{ 
  dev_t devnum; int ret; 
  //get command and pid 
  printk(KERN_INFO "(%s:pid=%d), %s : %s : %d\n", current->comm, current->pid, __FILE__, __func__, __LINE__); 
  //1. alloc cdev obj 
  demop = cdev_alloc(); 
  if(NULL == demop) { 
    return -ENOMEM; 
  }
    //2. init cdev obj
    cdev_init(demop, &fops);
    ret = alloc_chrdev_region(&devnum, minor, count, DEVNAME);
    if(ret){
        goto ERR_STEP;
    }
    major = MAJOR(devnum);
    //3. register cdev obj
    ret = cdev_add(demop, devnum, count);
    if(ret){
        goto ERR_STEP1;
    }
    //创建设备节点
    device_create(class_create(THIS_MODULE,DEVNAME),NULL,devnum,NULL,DEVNAME);
    printk(KERN_INFO "(%s:pid=%d), %s : %s : %d - ok.\n", current->comm, current->pid, __FILE__, __func__, __LINE__);
    return 0;

ERR_STEP1:
    unregister_chrdev_region(devnum, count);

ERR_STEP:
    cdev_del(demop);
    //get command and pid
    printk(KERN_INFO "(%s:pid=%d), %s : %s : %d - fail.\n", current->comm, current->pid, __FILE__, __func__, __LINE__);
    return ret;
}

static void __exit demo_exit(void)
{
    //get command and pid
    printk(KERN_INFO "(%s:pid=%d), %s : %s : %d - leave.\n", current->comm, current->pid, __FILE__, __func__, __LINE__);
    unregister_chrdev_region(MKDEV(major, minor), count);
    cdev_del(demop);
}

module_init(demo_init);
module_exit(demo_exit);

test.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int num, char *arg[])
{
    if(2 != num){
        printf("Usage: %s /dev/devfile\n", arg[0]);
        return -1;
    }
    int fd = open(arg[1], O_RDWR);
    if(0 > fd){
        perror("open");
        return -1;
    }
    getchar();
    int ret = read(fd, 0x321, 0);
    printf("read: ret = %d.\n", ret);
    getchar();
    ret = write(fd, 0x123, 0);
    printf("write: ret = %d.\n", ret);
    getchar();
    close(fd);
    return 0;
}

编译成功后,使用 insmod 命令加载:

然后用cat /proc/devices 查看,会发现设备号已经申请成功;


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值