您的位置:首页 > 运维架构 > Linux

Linux字符设备驱动

2013-07-11 17:09 211 查看
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方法都是可以工作的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: