该文章参考 http://blog.csdn.net/luoshengyang/article/details/6568411 老罗的笔记
也感谢以下文章
http://www.embedu.org/Column/Column433.htm 揭开linux内核中container_of的神秘面纱
http://blog.csdn.net/ghostyu/article/details/6876667 linux字符设备驱动 cdev
http://www.cnblogs.com/itech/archive/2012/05/15/2502284.html
Linux的inode的理解
这里只引用
传统的设备文件的方法来访问 这个方法来分析 如何实现的字符设备驱动
一下不明白的内容均用红色标注
hello.h
#ifndef _HELLO_ANDROID_H_
#define _HELLO_ANDROID_H_
#include <linux/cdev.h>
#include <linux/semaphore.h>
#define HELLO_DEVICE_NODE_NAME "hello"
#define HELLO_DEVICE_FILE_NAME "hello"
#define HELLO_DEVICE_PROC_NAME "hello"
#define HELLO_DEVICE_CLASS_NAME "hello"
struct hello_android_dev {
int val; //寄存器内容
struct semaphore sem; //互斥变量//同步访问信号量
struct cdev dev; //dev成员变量是一个内嵌的字符设备,这个Linux驱动程序自定义字符设备结构体的标准方法。//这里不用深入的了解,只要记住这个变量要将来要与方法绑定就OK了
};
#endif
这里没什么说的
主要说一下那个 struct cdev dev;
- /*
- *内核源码位置
- *linux2.6.38/include/linux/cdev.h
- */
-
- struct cdev {
- struct kobject kobj;
- struct module *owner; //一般初始化为:THIS_MODULE
- const struct file_operations *ops; //字符设备用到的例外一个重要的结构体file_operations,cdev初始化时与之绑定
- struct list_head list;
- dev_t dev; //dev_t为32位整形 设备号
- unsigned int count;
- };
Hello.c
- #include <linux/init.h>
- #include <linux/module.h>
- #include <linux/types.h>
- #include <linux/fs.h>
- #include <linux/proc_fs.h>
- #include <linux/device.h>
- #include <asm/uaccess.h>
-
- #include "hello.h"
-
-
- static int hello_major = 0;
- static int hello_minor = 0;
-
-
- static struct class* hello_class = NULL;
- static struct hello_android_dev* hello_dev = NULL;
-
- static int hello_open(struct inode* inode, struct file* filp);
- static int hello_release(struct inode* inode, struct file* filp);
- static ssize_t hello_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos);
- static ssize_t hello_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos);
-
-
- static struct file_operations hello_fops = {
- .owner = THIS_MODULE,
- .open = hello_open,
- .release = hello_release,
- .read = hello_read,
- .write = hello_write,
- };
将来的可执行文件里面的
标准read write open release 等方法 就映射到这上面
下面看方法的具体实现
/*打开设备*/
static int hello_open(struct inode* inode,
struct
file* filp)
{
struct hello_android_dev* dev;
/*将dev的首地址保存在filp中以便以后随便调用,其实我对于这里不是真正的理解*/
dev = container_of(inode->i_cdev, struct hello_android_dev, dev);
filp->private_data = dev;
return 0
}
/*设备文件释放时调用,空实现*/
- static int hello_release(struct inode* inode, struct file* filp) {
- return 0;
- }
/*读取设备的寄存器值val的值*/
static
ssize_t hello_read(
struct
file* filp,
char
__user *buf,
size_t
count, loff_t* f_pos)
{
ssize_t err = 0;
struct hello_android_dev* dev = filp->private_data;
if(copy_to_user(buf,&(dev->val),sizeof(
dev->val
)))
{
err = -EFAULT;
goto out;
}
err = sizeof(dev->val);
out:
return err;
}
/*写设备寄存器的值*/
static
ssize_t hello_write(
struct
file* filp,
const
char
__user *buf,
size_t
count, loff_t* f_pos)
{
struct hello_android_dev* dev = filp->private_data;
ssize_t err = 0;
if(cout != sizeof(dev->val))
{
goto out;
}
if(copy_from_user(&(dev->val),buf,count))
{
err = -EFAULT;
goto out;
}
err = sizeof(dev->val);
out:
return err;
}
上面的那些还是属于将来的驱动方法和加载没有任何关系。
下面是加载和卸载方法 加载时只要执行设备注册,和初始化操作就OK。
/*初始化设备*/
static int __hello_setup_dev(struct hello_android_dev* dev)
{
dev_t devno = MKDEV(hello_major,hello_minor);
//将major 和minor 合成这个dev_t
memset(dev,0,sizeof(struct hello_android_dev));
/*通过查询memset 发现这句在实际上是没有效果的,只是清楚了dev的内容*/
/*初始化cdev*/
cdev_init(&(dev->dev),&hello_fops);
dev->dev.owner = THIS_MODULE;
dev->dev.ops = &hello_fops;
/*注册字符设备*/
err = cdev_add(&(dev->dev),devno,1);
if(err)
{
return err;
}
/*初始化信号量和集群器的val 其实这里完全可以不用这个信号量*/
init_MUTEX(&(dev->sem));
dev->val = 0;
return 0;
}
/*模块加载*/
static ini __init hello_init(void)
{
int err = -1;
dev_t dev = 0;
struct device* temp = NULL;
printk(KERN_ALERT"Initializing hello device.\n");
/*动态分配主设备和从设备号*/
err = alloc_chrdev_region(&dev,0,1,HELLO_DEVICE_NODE_NAME);
if(err < 0)
{
printk(KERN_ALERT"Failed to alloc char dev region.\n");
goto fail;
}
hello_major = MAJOR(dev);
hello_minor = MINOR(dev);
/*分配hello设备结构体变量*/
hello_dev = kmalloc(sizeof(struct hello_android_dev),GFP_KERNEL);
if(!hello_dev)
{
err = -ENOMEM;
printk(KERN_ALERT"Failed to alloc hello_dev.\n");
goto unregister;
}
err = __hello_setup_dev(hello_dev);
if(err)
{
printk(KERN_ALERT"Failed to setup dev: %d.\n", err);
goto cleanup;
}
-
- hello_class = class_create(THIS_MODULE, HELLO_DEVICE_CLASS_NAME);
- if(IS_ERR(hello_class)) {
- err = PTR_ERR(hello_class);
- printk(KERN_ALERT"Failed to create hello class.\n");
- goto destroy_cdev;
- }
-
-
- temp = device_create(hello_class, NULL, dev, "%s", HELLO_DEVICE_FILE_NAME);
- if(IS_ERR(temp)) {
- err = PTR_ERR(temp);
- printk(KERN_ALERT"Failed to create hello device.");
- goto destroy_class;
- }
- destroy_cdev:
- cdev_del(&(hello_dev->dev));
- cleanup:
- kfree(hello_dev);
- unregister:
- unregister_chrdev_region(MKDEV(hello_major, hello_minor), 1);
}
-
- static void __exit hello_exit(void) {
- dev_t devno = MKDEV(hello_major, hello_minor);
-
- printk(KERN_ALERT"Destroy hello device.\n");
-
-
-
- if(hello_class) {
- device_destroy(hello_class, MKDEV(hello_major, hello_minor));
- class_destroy(hello_class);
- }
-
-
- if(hello_dev) {
- cdev_del(&(hello_dev->dev));
- kfree(hello_dev);
- }
-
-
- unregister_chrdev_region(devno, 1);
- }
- MODULE_LICENSE("GPL");
- MODULE_DESCRIPTION("First Android Driver");
-
- module_init(hello_init);
- module_exit(hello_exit);
几个小知识
1,
struct file_operations
该数据结构的主要作用就是把系统调用和驱动程序关联起来
file_operations结构的每一个成员的名字,都对应着一个系统调用,
用户进程利用系统调用,对设备文件进行诸如read/write操作时,系统调用通过设备文件的主设备号照相倒影的设备驱动程序,读取这个数据结构响应的函数指针
接着将控制权交给该函数。
这个是linux设备驱动程序工作的几本原理。
编写设备驱动程序的主要工作就是编写子函数,并填充file_operations的各个域
2,
alloc_chrdev_region 动态分配设备编号
int
alloc_chrdev_region(dev_t *dev,unsigned int -firstminor,unsigned int -count,char *name) 函数原型
该函数需要传递给它指定的第一次设备号firstminor(一般为0)和要分配的设备数(count) ,以及设备名(name),调用该函数后自动分配得到的设备号保存在dev中
3,
hello_dev = kmalloc(sizeof(struct hello_android_dev),GFP_KERNEL);
void *
kmalloc
(size_t size, int flags) 函数原型
设备 驱动程序
或者 内核
模块中动态开辟内存,
释放内存用的是kfree
4,
cdev_init
函数原型
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev);//清空cdev的控件,也是后初始化内容赋空
INIT_LIST_HEAD(&cdev->list);
kobject_init(&cdev->kobj, &ktype_cdev_default);
cdev->ops = fops;//连接fops
}
5,
cdev_add
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
p->dev = dev;
p->count = count;
return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}
6,
cdev_del
void cdev_del(struct cdev *p)
{
cdev_unmap(p->dev, p->count);
kobject_put(&p->kobj);
}
字符设备的注册过程
1,初始化cdev 分为静态和动态,上面使用的是静态
也就是上面调用的
cdev_init();
2,初始化以后,需要将cdev添加到系统中去,调用cdev_add()函数,传入cdev结构体指针,起始设备编号,以及设备数(设备号范围)
3,当一个字符设备驱动不再需要的时候,就可以cdev_del()函数来释放cdev占用的内存
4,创建字符设备的class
5,创建设备
然后就是 加载,卸载的那些事了
6,模块卸载,销毁设备类别和设备,删除字符设备和释放设备内存,释放设备号
-
- if(hello_class) {
- device_destroy(hello_class, MKDEV(hello_major, hello_minor));
- class_destroy(hello_class);
- }
-
-
- if(hello_dev) {
- cdev_del(&(hello_dev->dev));
- kfree(hello_dev);
- }
-
-
- unregister_chrdev_region(devno, 1);
http://blog.csdn.net/yangdelong/article/details/5493145
感谢百度,感谢谷歌!