Linux字符设备驱动

1. 关于设备号
设备号分主设备号和次设备号,主设备号用来标识设备所对应的驱动程序,由于同一个驱动程序可以支持多个设备,次设备号就是用来区分采用同一个驱动程序的不同设备。

Linux内核中采用dev_t来描述设备号,该类型定义在linux/types.h中,阅读内核源码可以看出dev_t实质是一个32位无符号整型,其中使用高12位为设备号,低20位为次设备号,Linux提供了两个宏MAJOR和MINOR用来从dev_t中分解出主设备号和次设备号,将主设备号和次设备号合并为dev_t类型,Linux也提供了宏MKDEV。

2. 设备号的申请(linux/fs.h)
(1) 静态申请
函数原型如下:
int register_chrdev_region(dev_t from, unsigned count, const char *name);
from为设备号起始值,form中的次设备号通常被设置成0,count是所请求连续设备号个数,name是设备号范围关联名称,它将出现在/proc/device和sysfs中。申请成功返回0,失败返回一个负的错误码,并且不能使用所请求的设备号。

(2) 动态申请
register_chrdev_region函数是在已有设备号基础上向内核申请对该设备号的使用,而动态申请是由内核分配一个设备号,函数原型如下:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
申请成功的设备号保存在dev中,baseminor为起始次设备号。

(3) 释放
不再使用该设备号时需要释放设备号,函数原型如下:
void unregister_chrdev_region(dev_t from, unsigned count);

3. 字符设备驱动的注册
Linux内核使用struct cdev结构来表示字符设备,该结构原型定义在linux/cdev.h中,该结构定义如下:

struct cdev {
        struct kobject kobj;
        struct module *owner;
        const struct file_operations *ops;
        struct list_head list;
        dev_t dev;
        unsigned int count;
};


(1) cdev结构体初始化
cdev结构体申请有两种方式,一是静态分配cdev,二是动态分配。例如:
struct cdev my_cdev;
cdev_init(&my_cdev, &my_fops);
my_cdev.owner = THIS_MODULE;

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


(2) 字符设备驱动注册
注册字符设备驱动使用cdev_add函数,函数原型如下:
int cdev_add(struct cdev *p, dev_t dev, unsigned count);

(3) 字符设备驱动注销
注销字符设备驱动使用cdev_del函数,函数原型如下:
void cdev_del(struct cdev *p);

4. 其它
早期有两个经典的字符设备驱动注册与注销函数register_chrdev和unregister_chrdev,函数原型如下:
int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops);
void unregister_chrdev(unsigned int major, const char *name);


// 2015.09.02 add
下面是一个字符设备驱动的实例:

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

#define MEM_SIZE	4096 /* 4K */

struct mem_dev {
	struct cdev cdev;
	char mem[MEM_SIZE];
};

struct mem_dev dev;
dev_t devno;

static ssize_t
mem_read(struct file *file, char __user *buf,
			size_t count, loff_t *pos)
{
	unsigned long p = *pos;
	int ret = 0;

	if (p >= MEM_SIZE)
		return 0;

	if (count > (MEM_SIZE - p))
		count = MEM_SIZE - p;

	ret = copy_to_user(buf, (void *)(dev.mem + p), count);
	if (ret)
		return -EFAULT;
	else {
		*pos += count;
		return count;
	}
}

static ssize_t
mem_write(struct file *file, const char __user *buf,
			size_t count, loff_t *pos)
{
	unsigned long p = *pos;
	int ret = 0;

	if (p >= MEM_SIZE)
		return 0;

	if (count > (MEM_SIZE - p))
		count = MEM_SIZE - p;

	ret = copy_from_user((void *)(dev.mem + p), buf, count);
	if (ret)
		return -EFAULT;
	else {
		*pos += count;
		return count;
	}
}

static struct file_operations mem_fops = {
	.owner		= THIS_MODULE,
	.read		= mem_read,
	.write		= mem_write,
};

static int __init mem_init(void)
{
	int ret = 0;

	ret = alloc_chrdev_region(&devno, 0, 1, "memory");
	if (ret < 0) {
		return ret;
	}

	cdev_init(&dev.cdev, &mem_fops);
	dev.cdev.owner = THIS_MODULE;

	ret = cdev_add(&dev.cdev, devno, 1);
	if (ret < 0) {
		unregister_chrdev_region(devno, 1);
		return ret;
	}

	return 0;
}

static void __exit mem_exit(void)
{
	cdev_del(&dev.cdev);
	unregister_chrdev_region(devno, 1);
}

module_init(mem_init);
module_exit(mem_exit);

MODULE_LICENSE("GPL");
编译生成.ko文件之后,使用insmod命令加载该模块,由于设备号是动态分配的,所以需要在/proc/devices找到主设备号,cat /proc/devices得到结果如下:
244 memory

那么244就是该模块的主设备号,然后需要在/dev目录下创建一个设备节点,命令如下:
mknod /dev/memory c 244 0

最后测试一下驱动,echo "hello world" > /dev/memory,然后cat一下,得到结果:
hello world

说明驱动中的read、write方法都是可以工作的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值