最近在看国嵌的字符驱动的教学,可能是刚刚入手驱动很不适应,所以将一些知识点总结如下:
从如下几个方面进行总结 1.字符驱动基础知识 和.字符驱动程序设计的流程 2.字符驱动的过程(如何从应用层控制内核模块的 vsf文件系统) 3.部分琐碎知识点总结 4.我写的代码分析(此代码经过测试成功的)。
1.字符驱动的基础知识:a.设备号 ( 获得主设备号 MAJOR(dev_t dev) 获得次设备号MINOR(dev_t dev) 获得设备号MKDEV(mem_major,mem_minor) )
b.申请设备号(相关函数有动态分配,静态申请,注销设备号)
c.设备文件的创建(自动创建,手动创建mknod chardev c 251 0 )
d.有关的三个重要结构(struct file 包含f_pos和f_operation struct inode 、包含文件的物理信息 设备号 struct file_operation 包含底层的文件操作)
e.设备的注册分三步(1.设备的分配 2.设备的初始化 3.设备的添加 4.设备的注销)
f.实现file_operation中的操作
g.设备注销 设备号注销
2.字符驱动的过程:驱动部分 a先获得了设备号和设备名(此处注意设备名是/proc/devices中和设备号对应的名字 不是/dev路径下的文件的名字 两者区别为一个主设备号对应一个设备名 一个次设备号对应一个设备文件名 也就是说一个次设备号对应一个设备 一个主设备号对应一类设备),b然后创建设备文件,创建设备文件时用到了设备号,设备号又与设备文件联系起来(应该是保存到了file inode里面了即文件路径与设备号的关系),c注册设备的时候其实是将设备号和file_operation连接起来,这样设备文件就与file_operation建立起关系来了(inode与operation)。
应用部分 a. fopen("路径","读写方式") 通过路径----->inode---->设备号----->operation----->struct file 通过vsf文件系统找到mem_open("inode","struct file") 在此函数中通过inode找到次设备号 通过struct file找到private_data这个空指针(次指针是用来保存想要传达给read和write的内存空间) 然后我推测两个东西 1.读写方式的控制是通过对operation的read和write赋值为NULL造成的 2.fopen的返回值是FILE *fp 这个结构应该有inode的路径参数或者是设备号 所以fread和fwrite才能找到对应的operation 而且还应该有struct file里面f_pos参数 因为这样才能使fread和fwrite有文件位置指针(但是有一个问题就是应用层的fwrite或fread通过各种途径去找到mem_read或mem_write ,但是有没有操作设备文件 ,设备文件和字符驱动模块即开辟的内存有什么关系,还是设备文件仅仅提供一个inode cat一下设备文件发现里面的内容和写入字符驱动内存里面的数据是一样的难道两者是同样的东东)。老师说在操作设备的时候 实际就是在操作设备节点
b.fwrite(buf,sizeof(buf),1,fp)传统的fwrite即file *fp不是设备文件的时候,应该是找fopen提供的file *fp---->struct file---->operation(通过vsf虚拟文件系统找到,代码在linux内核的Read_write.c文件中),operation中的write应该是对底层存储设备如flash或者是硬盘进行写操作,而我们的字符驱动的operation中的write则是对内存进行操作。mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)。
c.fread与fwrite相同。
d.fseek也是通过FILE *fp去找到struct file---->operation
3.琐碎知识(遇到的不会的问题):a.设备文件和字符驱动模块内存的问题 (已答见上面)
b.mem_write函数中*ppos能不能用file->f_pos代替吗?(我觉得是一个东西虽然没有内核依据)
c.memdev_exit函数中释放开辟的内存空间的问题 (我觉得是国嵌老师最后忘记释放内存了)
d.注意这里C99标准对结构体整体赋值的方法,挺有意思
4.我的代码(已经写好备注)
驱动程序memdev.c:
[cpp] view plaincopy
- /**************************************************************************
- 文件名: memdev.c
- 日期: 2013/05/12 17:21
- 头文件: memdev.h
- 功能: 简单字符驱动(开辟一块内存当做字符驱动进行读写) 此为驱动部分
- 环境: Redhat企业版5 内核版本2.6.18-53.el5
- 作者: Hao
- 流程: 1.分配设备号(a.静态申请 b.动态分配)
- 2.创建设备文件(a.手动创建mknod(需要设备号)注“设备名和设备文件名” b.自动创建)
- 3.设备注册(a.设备注册分配 b.设备注册初始化 c.设备注册添加)
- 4.实现file_operation中的函数
- 5.设备注销(cdev_del)
- 6.设备号注销
- ***************************************************************************/
- #include <linux/module.h>
- #include <linux/types.h>
- #include <linux/fs.h>
- #include <linux/errno.h>
- #include <linux/mm.h>
- #include <linux/sched.h>
- #include <linux/init.h>
- #include <linux/cdev.h>
- #include <linux/slab.h>
- #include <asm/io.h>
- #include <asm/system.h>
- #include <asm/uaccess.h>
- #include "memdev.h"
- MODULE_AUTHOR("Hao");
- MODULE_LICENSE("GPL");
- static
int mem_major=MEMDEV_MAJOR; //定义主设备号(定义一个全局变量在read write memdev_exit中都能用 )
- module_param(mem_major, int, S_IRUGO);//接收模块参数主设备号
- struct mem_dev *mem_devp; //定义mem设备描述结构体指针
- struct cdev c_dev; //设备注册分配 替换了struct cdev *cdev_alloc() 全局变量都可以使用
- /**************************************************************************
- 函数名: mem_open
- 函数功能: 文件打开函数 使private_data指向 字符驱动模块
- 函数参数: inode存储文件物理信息的结构(包含设备号) file结构
- 函数返回值: 返回0为正常执行
- ***************************************************************************/
- int mem_open(struct inode *inode,struct file *filp)
- {
- struct mem_dev *dev;
- int num = MINOR(inode->i_rdev); //获得次设备号
- if (num >= MEMDEV_NR_DEVS)
- return -ENODEV; //判断是否次设备号 不正确
- dev = &mem_devp[num];
- filp->private_data = dev; /*不是很了解private_data
- 宋宝华的linux设备驱动开发详解,93页写到私有数据指针private_data在设备驱动中背广泛使用,大多数指向设备驱动自定义用于描述设备的结构体。*/
- return 0;
- }
- /**************************************************************************
- 函数名: mem_release
- 函数功能: 文件关闭函数
- 函数参数: inode存储文件物理信息的结构(包含设备号) file结构
- 函数返回值: 返回0为正常执行
- ***************************************************************************/
- int mem_release(struct inode *inode, struct file *filp)
- {
- return 0;
- }
- /**************************************************************************
- 函数名: mem_read
- 函数功能: 文件读取函数
- 函数参数: struct file *filp(open的时候系统产生的结构,根据inode来的)
- char __user *buf 存储读取数据的空间
- size_t size 读取的大小 size_t应该就是typedef unsigned int的类型
- loff_t *ppos 当前文件指针的位置
- 函数返回值: 返回ret 正常应该是size的值
- ***************************************************************************/
- static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
- {
- unsigned long p = *ppos; //保存下文件指针的当前位置
- unsigned int count=size;
- int ret = 0;
- struct mem_dev *dev = filp->private_data; //private_data是一个空指针用来接收各种数据的传递
- if (p >= MEMDEV_SIZE) //判断文件指针是否有效
- return 0;
- if (count > MEMDEV_SIZE - p)//判断读取的大小是否比剩余的内存大小还大
- count = MEMDEV_SIZE - p;
- if (copy_to_user(buf, (void*)(dev->data + p), count))//把字符设备模块从内核空间拷贝到用户空间
- {
- ret = - EFAULT;
- }
- else
- {
- *ppos += count;
- ret = count;
- printk(KERN_EMERG "read %d bytes(s) from %ld\n", count, p);
- }
- return ret;
- }
- /**************************************************************************
- 函数名: mem_write
- 函数功能: 文件写函数
- 函数参数: 参数同read函数
- 注意:struct file *filp,char __user *buf和size_t size是内核从应用层api-fread中传递下来的
- 具体是怎么传递的不详 看内核代码 Read_write.c
- 函数返回值: 返回size为正常执行
- ***************************************************************************/
- static ssize_t mem_write(struct file *filp, const
char __user *buf, size_t size, loff_t *ppos) - {
- unsigned long p = *ppos;
- unsigned int count = size;
- int ret = 0;
- struct mem_dev *dev = filp->private_data;
- if (p >= MEMDEV_SIZE)//MEMDEV_SIZE为内存最大值
- return 0;
- if (count > MEMDEV_SIZE - p)
- count = MEMDEV_SIZE - p;
- if (copy_from_user(dev->data + p, buf, count)) //从应用程序的角度看写是从用户到内核
- ret = - EFAULT;
- else {
- *ppos += count;// <span style="color:#ff0000;">不知道这里用filp->f_pos+=count行不行 因为我觉得filp->f_pos和ppos是一个地址</span>
- ret = count;
- printk(KERN_EMERG "written %d bytes(s) from %ld\n", count, p);
- }
- return ret;
- }
- /**************************************************************************
- 函数名: mem_llseek
- 函数功能: 文件位置指针定位函数
- 函数参数: struct file *filp
- loff_t offset偏移量
- int whence 三种可能SEEK_SET SEEK_CUR SEEK_END
- 函数返回值: 返回0为正常执行
- ***************************************************************************/
- static loff_t mem_llseek(struct file *filp, loff_t offset, int whence)
- {
- loff_t newpos; //定义中间变量
- switch(whence) {
- case 0: /* SEEK_SET */
- newpos = offset;
- break;
- case 1: /* SEEK_CUR */
- newpos = filp->f_pos + offset;
- break;
- case 2: /* SEEK_END */
- newpos = MEMDEV_SIZE -1 + offset;
- break;
- default: /* can't happen */
- return -EINVAL;
- }
- if ((newpos<0) || (newpos>MEMDEV_SIZE)) //判断newpos是否有效
- return -EINVAL;
- filp->f_pos = newpos; //赋值 定位
- return newpos;
- }
- static
const
struct file_operations mem_fops = //定义此字符设备的file_operations
- { //这里是对结构体整体赋值的方式
- .owner = THIS_MODULE,
- .llseek = mem_llseek, //函数名都可以自己定义 都是函数指针
- .read = mem_read,
- .write = mem_write,
- .open = mem_open,
- .release = mem_release,
- };
- /**************************************************************************
- 函数名: memdev_init
- 函数功能: 内核模块入口函数
- 函数参数: 无
- 函数返回值: 返回0为正常执行 返回result为设备号获取不成功
- 返回- ENOMEM为内存分配不成功
- ***************************************************************************/
- static
int memdev_init() - {
- int i;
- int result;
- dev_t dev_num=MKDEV(mem_major,0);//将主设备号和次设备号转换成32位的设备号给 静态申请设备号用的
- if(mem_major)
- result=register_chrdev_region(dev_num,2,"newmemdev");//静态申请设备号为dev_num 2个设备 设备名为“newmemdev”
- else
- {
- result=alloc_chrdev_region(&dev_num,0,2,"newmemdev");//动态分配设备号 设备名可以在/proc/devices文件中找 与/dev路径找文件不同 因为一个设备文件 一个次设备号
- mem_major = MAJOR(dev_num);//获得主设备号
- }
- if (result < 0)
- {
- return result; //判断动态分配是否成功 不成功则退出函数
- }
- /*设备注册 分配已经在前面完成了 为全局变量*/
- cdev_init(&c_dev,&mem_fops);//设备注册初始化 将设备号dev_num和file_operation建立联系
- c_dev.owner = THIS_MODULE;//*****************貌似可以屏蔽吧*********************
- cdev_add(&c_dev,dev_num,2);//设备注册 添加 设备号为dev_num 设备数为2个
- mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);//开辟2个字符设备模块大小的空间
- if (!mem_devp) /*判断是否分配内存成功*/
- {
- result = - ENOMEM;
- goto fail_malloc;
- }
- memset(mem_devp, 0, MEMDEV_NR_DEVS * sizeof(struct mem_dev)); //将初始化2个字符设备模块大小的空间清零
- for (i=0; i < MEMDEV_NR_DEVS; i++) //对字符设备模块结构赋值
- {
- mem_devp.size = MEMDEV_SIZE;
- mem_devp.data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);//因为data是指针所以继续对其开辟4k空间了
- memset(mem_devp.data, 0, MEMDEV_SIZE);
- }
- return 0;
- fail_malloc: //如果内存分配不成功直接注销设备号
- unregister_chrdev_region(dev_num, 1);/*但是不明白的是 1.为什么只注销一个次设备的设备号 2.为什么不先注销cdev_dev*/
- return result;//虽然这里不懂 但不深究了 因为很少出现内存分配不成功的情况(在学习练习的时候)
- }
- /**************************************************************************
- 函数名: memdev_exit
- 函数功能: 内核模块退出函数
- 函数参数: 无
- 函数返回值: 无
- ***************************************************************************/
- static
void memdev_exit(void) - {
- cdev_del(&c_dev);//设备注销
- kfree(mem_devp);//释放开辟的内存空间
- <span style="color:#ff0000;">/*这里貌似没有释放mem_devp.data的指针 是不是应该kfree(mem_devp[1].data)*/
- /*这里有一个问题就是那两个内存分配 为什么要分配两次 为什么释放一次 我试过貌似第二次不分配也可以*/</span>
- unregister_chrdev_region(MKDEV(mem_major, 0), 2);//设备号注销
- }
- module_init(memdev_init);
- module_exit(memdev_exit);
memdev.h:
[cpp] view plaincopy
- /**************************************************************************
- 文件名: memdev.c
- 日期: 2013/05/12 17:21
- 头文件: memdev.h
- 功能: 简单字符驱动(开辟一块内存当做字符驱动进行读写) 此为驱动部分
- 环境: Redhat企业版5 内核版本2.6.18-53.el5
- 作者: Hao
- 流程: 1.分配设备号(a.静态申请 b.动态分配)
- 2.创建设备文件(a.手动创建mknod(需要设备号)注“设备名和设备文件名” b.自动创建)
- 3.设备注册(a.设备注册分配 b.设备注册初始化 c.设备注册添加)
- 4.实现file_operation中的函数
- 5.设备注销(cdev_del)
- 6.设备号注销
- ***************************************************************************/
- #ifndef _MEMDEV_H_
- #define _MEMDEV_H_
- #ifndef MEMDEV_MAJOR
- #define MEMDEV_MAJOR 251 /*预设的mem的主设备号*/
- #endif
- #ifndef MEMDEV_NR_DEVS
- #define MEMDEV_NR_DEVS 2 /*设备数*/
- #endif
- #ifndef MEMDEV_SIZE
- #define MEMDEV_SIZE 4096 /*开辟的内存大小*/
- #endif
- /*mem设备描述结构体*/
- struct mem_dev
- {
- char *data;
- unsigned long size;
- };
- #endif /* _MEMDEV_H_ */
应用程序appmem.c:
[cpp] view plaincopy
- #include<stdio.h>
- #include<string.h>
- int main()
- {
- FILE *fp;
- char buf[4096]="My frist char dev!!!";
- fp=fopen("/dev/chardev0","r+");//字符设备文件名为chardev0 所以在/dev 路径中手动创建设备文件时候名字要一致
- if (fp == NULL)
- {
- printf("Open chardev0 Error!\n");
- return -1;
- }
- fwrite(buf,sizeof(buf),1,fp); //往字符驱动里面写"My frist char dev!!!"
- printf("finish write!!\n");
- fseek(fp,0,SEEK_SET);//位置清零
- printf("finish seek!!\n");
- strcpy(buf, "Buf is NULL!");//因为使用同一个buf所以要先清除原值
- printf("buf: %s\n",buf);
- fread(buf,sizeof(buf),1,fp);
- printf("finish read!!\n");
- printf("new read buf is %s\n",buf);
- return 0;
- }
Makefile:[cpp] view plaincopy
- ifneq ($(KERNELRELEASE),)
- obj-m := memdev.o
- else
- KDIR :=/lib/modules/2.6.18-53.el5/build
- all:
- make -C $(KDIR) M=$(PWD) modules
- clean:
- rm -f *.ko *.o *.mod.o *.mod.c *.symvers
- endif
事后老师答疑:1.在操作设备的时候 实际就是在操作设备节点
2.关于那个内存释放的问题 是这样第一,没有malloc那个char *的时候 abc->data="hello!!" 不出现段错误是因为hello是一个地址 赋值给指针没错误 但是要想能够释放这段内存最好还是先malloc后再free(习惯好)。第二,一定要进行两次free, free结构体紧紧释放了结构体中保存那个char *指针的空间(即4个字节),所以应该先free结构体中的char *开辟的空间,再free结构体的大小。
日后补充:1.设备文件和设备名
设备文件又叫设备节点,在/dev路径下,一个次设备号一个设备文件,即一个设备一个设备文件,在open函数中要注意路径要与设备文件相对应。设备名与主设备号对应,在/proc/devices中可以查询,一个设备名一类设备。 |