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

linux设备驱动--globalmem字符设备框架分析

2015-04-26 09:11 651 查看
目录(?)[+]

linux设备驱动--globalmem字符设备框架分析

有不对,不足,需要改正的地方还望大家指出,而且希望结识志同道合的朋友一起学习技术,共同进步.

功能: 1.设备驱动开发详解-globalmem字符设备框架分析(支持2个设备)

目录: 1. globalmem流程图

2.源码

3.分析

1)MKDEV

2)kmalloc和vmalloc的区别

3)module_param

4)file文件结构

5)inode结构

6)container_of()

7)ioctl函数

实现: globalmem流程图



源码:

globalmen模块

[cpp] view
plaincopy

#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<linux/kdev_t.h>

#include<linux/device.h>

#include <asm/io.h>

#include<asm/system.h>

#include<asm/uaccess.h>

#define DEVICE_NAME "globalmem"

#defineGLOBALMEM_SIZE 0x1000 /*全局内存最大4K字节*/

#define MEM_CLEAR0x1 /*清0全局内存*/

#define GLOBALMEM_MAJOR241 /*预设的globalmem的主设备号*/

static intglobalmem_major = GLOBALMEM_MAJOR;

/*globalmem设备结构体*/

struct globalmem_dev

{

struct cdev cdev; /*cdev结构体*/

unsigned char mem[GLOBALMEM_SIZE];/*全局内存*/

};

struct globalmem_dev*globalmem_devp; /*设备结构体指针*/

static struct class*globalmem0_class;

static struct class*globalmem1_class;

/*文件打开函数*/

int globalmem_open(structinode *inode, struct file *filp)

{

/*将设备结构体指针赋值给文件私有数据指针*/

struct globalmem_dev *dev;

dev = container_of(inode->i_cdev,structglobalmem_dev,cdev);

filp->private_data = dev;

return 0;

}

/*文件释放函数*/

intglobalmem_release(struct inode *inode, struct file *filp)

{

return 0;

}

/* ioctl设备控制函数 */

static intglobalmem_ioctl(struct inode *inodep, struct file *filp, unsigned

int cmd, unsigned long arg)

{

struct globalmem_dev *dev =filp->private_data;/*获得设备结构体指针*/

switch (cmd)

{

case MEM_CLEAR:

memset(dev->mem, 0,GLOBALMEM_SIZE);

printk(KERN_INFO "globalmem is setto zero\n");

break;

default:

return - EINVAL;

}

return 0;

}

/*读函数*/

static ssize_tglobalmem_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 globalmem_dev *dev =filp->private_data; /*获得设备结构体指针*/

/*分析和获取有效的写长度*/

if (p >= GLOBALMEM_SIZE)

return count ? - ENXIO: 0;

if (count > GLOBALMEM_SIZE - p)

count = GLOBALMEM_SIZE - p;

/*内核空间->用户空间*/

if (copy_to_user(buf, (void*)(dev->mem +p), count))

{

ret = - EFAULT;

}

else

{

*ppos += count;

ret = count;

printk(KERN_INFO "read %d bytes(s)from %d\n", count, p);

}

return ret;

}

/*写函数*/

static ssize_tglobalmem_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 globalmem_dev *dev =filp->private_data; /*获得设备结构体指针*/

/*分析和获取有效的写长度*/

if (p >= GLOBALMEM_SIZE)

return count ? - ENXIO: 0;

if (count > GLOBALMEM_SIZE - p)

count = GLOBALMEM_SIZE - p;

/*用户空间->内核空间*/

if (copy_from_user(dev->mem + p, buf,count))

ret = - EFAULT;

else

{

*ppos += count;

ret = count;

printk(KERN_INFO "written %d bytes(s)from %d\n", count, p);

}

return ret;

}

/* seek文件定位函数 */

static loff_tglobalmem_llseek(struct file *filp, loff_t offset, int orig)

{

loff_t ret = 0;

switch (orig)

{

case 0: /*相对文件开始位置偏移*/

if (offset < 0)

{

ret = - EINVAL;

break;

}

if ((unsigned int)offset >GLOBALMEM_SIZE)

{

ret = - EINVAL;

break;

}

filp->f_pos = (unsigned int)offset;

ret = filp->f_pos;

break;

case 1: /*相对文件当前位置偏移*/

if ((filp->f_pos + offset) >GLOBALMEM_SIZE)

{

ret = - EINVAL;

break;

}

if ((filp->f_pos + offset) < 0)

{

ret = - EINVAL;

break;

}

filp->f_pos += offset;

ret = filp->f_pos;

break;

default:

ret = - EINVAL;

break;

}

return ret;

}

/*文件操作结构体*/

static const structfile_operations globalmem_fops =

{

.owner = THIS_MODULE,

.llseek = globalmem_llseek,

.read = globalmem_read,

.write = globalmem_write,

.ioctl = globalmem_ioctl,

.open = globalmem_open,

.release = globalmem_release,

};

/*初始化并注册cdev*/

static voidglobalmem_setup_cdev(struct globalmem_dev *dev, int index)

{

int err, devno = MKDEV(globalmem_major,index);

cdev_init(&dev->cdev,&globalmem_fops);

dev->cdev.owner = THIS_MODULE;

dev->cdev.ops = &globalmem_fops;

err = cdev_add(&dev->cdev, devno, 1);

if (err)

printk(KERN_NOTICE "Error %d addingLED%d", err, index);

}

/*设备驱动模块加载函数*/

int globalmem_init(void)

{

int result;

dev_t devno = MKDEV(globalmem_major, 0);

/* 申请设备号*/

if (globalmem_major)

result = register_chrdev_region(devno, 2,DEVICE_NAME);

else /* 动态申请设备号 */

{

result = alloc_chrdev_region(&devno, 0,2, DEVICE_NAME);

globalmem_major = MAJOR(devno);

}

if (result < 0)

return result;

/* 动态申请2个设备结构体的内存*/

globalmem_devp = kmalloc(2*sizeof(structglobalmem_dev), GFP_KERNEL);

if (!globalmem_devp) /*申请失败*/

{

result = - ENOMEM;

goto fail_malloc;

}

memset(globalmem_devp, 0, 2*sizeof(structglobalmem_dev));

globalmem_setup_cdev(&globalmem_devp[0],0);

globalmem_setup_cdev(&globalmem_devp[1],1);

//注册一个类,使mdev可以在/dev/下面建立设备节点

globalmem0_class =class_create(THIS_MODULE, "globalmem0");

if( IS_ERR(globalmem0_class) )

{

printk(KERN_NOTICE, "creatglobalmem0_class failed!");

return -1;

}

//创建一个设备节点,节点名字为globalmem0

device_create(globalmem0_class, NULL,MKDEV(globalmem_major, 0), NULL, "globalmem0");

printk(KERN_NOTICE, "globalmem0initialized!");

globalmem1_class =class_create(THIS_MODULE, "globalmem1");

if( IS_ERR(globalmem1_class) )

{

printk(KERN_NOTICE, "creatglobalmem1_class failed!");

return -1;

}

//创建一个设备节点,节点名字为globalmem1

device_create(globalmem1_class, NULL,MKDEV(globalmem_major, 1), NULL, "globalmem1");

printk(KERN_NOTICE, "globalmem1initialized!");

return 0;

fail_malloc: unregister_chrdev_region(devno,2);

return result;

}

/*模块卸载函数*/

void globalmem_exit(void)

{

cdev_del(&(globalmem_devp[0].cdev));

cdev_del(&(globalmem_devp[1].cdev)); /*注销cdev*/

kfree(globalmem_devp); /*释放设备结构体内存*/

unregister_chrdev_region(MKDEV(globalmem_major, 0), 2); /*释放设备号*/

class_destroy(globalmem0_class);

class_destroy(globalmem1_class);

}

MODULE_AUTHOR("SongBaohua");

MODULE_LICENSE("DualBSD/GPL");

module_param(globalmem_major,int, S_IRUGO);

module_init(globalmem_init);

module_exit(globalmem_exit);

测试程序:

[cpp] view
plaincopy

#include <stdio.h>

#include <stdlib.h>

#include <time.h>

#include <unistd.h>

#include<linux/fcntl.h>

int main()

{

int fd;

char buf1[]="testglobalmen data!";//写入memdev设备的内容

char buf_read[4096]; //memdev设备的内容读入到该buf中

if((fd=open("/dev/globalmem0",O_RDWR))==-1)//打开memdev设备

{

printf("open globalmem0 WRONG!\n");

return 0;

}

int count = 0;

while(count<=100)

{

printf("openglobalmem0 SUCCESS!\n");

printf("buf is %s\n",buf1);

write(fd,buf1,sizeof(buf1));//把buf中的内容写入memdev设备

lseek(fd,0,SEEK_SET); //把文件指针重新定位到文件开始的位置

read(fd,buf_read,sizeof(buf1));//把memdev设备中的内容读入到buf_read中

printf("buf_read is %s\n",buf_read);

count++;

}

close(fd);

return 0;

}

分析:

一.MKDEV

[cpp] view
plaincopy

/linux/kdev_t.h

#define _LINUX_KDEV_T_H

3#ifdef __KERNEL__

4#define MINORBITS 20

5#define MINORMASK ((1U << MINORBITS) - 1)

6

7#define MAJOR(dev) ((unsigned int) ((dev) >>MINORBITS))

8#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))

9#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))

当编译内核的时候,__KERNEL__在命令行被定义

When you compile your kernel, __KERNEL__ isdefined on the command line.

User-space programs need access tothe kernel headers, but some of the info in kernel headers is intended only forthe kernel. Wrapping some statements in an #ifdef __KERNEL__/#endif blockensures that user-space programs don't
see those statements.

主设备号高12位,次设备号低20位

二.kmalloc和vmalloc的区别

1.kmalloc

1>kmalloc内存分配和malloc相似,除非被阻塞否则他执行的速度非常快,而且不对获得空间清零.在用kmalloc申请函数后,要对起清零.用memset()函数对申请的内存进行清零。

2>kamlloc函数原型:

#include<linux/slab.h>

Void *kmalloc(size_t size, intflags);

(1)第一个参数是要分配的块的大小

(2)第二个参数是分配标志(flags),他提供了多种kmalloc的行为。

(3)第三个最常用的GFP_KERNEL;

A.表示内存分配(最终总是调用get_free_pages来实现实际的分配;这就是GFP前缀的由来)是代表运行在内核空间的进程执行的。使用GFP_KERNEL容许kmalloc在分配空闲内存时候如果内存不足容许把当前进程睡眠以等待。因此这时分配函数必须是可重入的。如果在进程上下文之外如:中断处理程序、tasklet以及内核定时器中这种情况下current进程不该睡眠,驱动程序该使用GFP_ATOMIC.

B.GFP_ATOMIC

用来从中断处理和进程上下文之外的其他代码中分配内存. 从不睡眠.

C.GFP_KERNEL

内核内存的正常分配. 可能睡眠.

D.GFP_USER

用来为用户空间页来分配内存; 它可能睡眠.

E.GFP_HIGHUSER

如同 GFP_USER, 但是从高端内存分配, 如果有. 高端内存在下一个子节描述.

F.GFP_NOFS,GFP_NOIO

这个标志功能如同 GFP_KERNEL, 但是它们增加限制到内核能做的来满足请求. 一个 GFP_NOFS 分配不允许进行任何文件系统调用, 而 GFP_NOIO 根本不允许任何 I/O 初始化. 它们主要地用在文件系统和虚拟内存代码, 那里允许一个分配睡眠, 但是递归的文件系统调用会是一个坏注意.

上面列出的这些分配标志可以是下列标志的相或来作为参数, 这些标志改变这些分配如何进行:

__GFP_DMA

这个标志要求分配在能够 DMA 的内存区. 确切的含义是平台依赖的并且在下面章节来解释.

__GFP_HIGHMEM

这个标志指示分配的内存可以位于高端内存.

__GFP_COLD

正常地, 内存分配器尽力返回"缓冲热"的页 -- 可能在处理器缓冲中找到的页. 相反, 这个标志请求一个"冷"页, 它在一段时间没被使用. 它对分配页作 DMA 读是有用的, 此时在处理器缓冲中出现是无用的. 一个完整的对如何分配 DMA 缓存的讨论看"直接内存存取"一节在第 1 章.

__GFP_NOWARN

这个很少用到的标志阻止内核来发出警告(使用 printk), 当一个分配无法满足.

__GFP_HIGH

这个标志标识了一个高优先级请求, 它被允许来消耗甚至被内核保留给紧急状况的最后的内存页.

Ø __GFP_REPEAT

__GFP_NOFAIL

__GFP_NORETRY

这些标志修改分配器如何动作, 当它有困难满足一个分配. __GFP_REPEAT 意思是" 更尽力些尝试" 通过重复尝试 -- 但是分配可能仍然失败. __GFP_NOFAIL 标志告诉分配器不要失败; 它尽最大努力来满足要求. 使用__GFP_NOFAIL 是强烈不推荐的; 可能从不会有有效的理由在一个设备驱动中使用它. 最后, __GFP_NORETRY告知分配器立即放弃如果得不到请求的内存.

Ø 内存区段

__GFP_DMA和__GFP_HIGHMEM的使用与平台相关,Linux把内存分成3个区段:可用于DMA的内存、常规内存、以及高端内存。X86平台上ISA设备DMA区段是内存的前16MB,而PCI设备无此限制。

内存区后面的机制在 mm/page_alloc.c 中实现, 而内存区的初始化在平台特定的文件中, 常常在 arch 目录树的 mm/init.c。

3>kamlloc的使用方法:

Linux 处理内存分配通过创建一套固定大小的内存对象池. 分配请求被这样来处理, 进入一个持有足够大的对象的池子并且将整个内存块递交给请求者. 驱动开发者应当记住的一件事情是, 内核只能分配某些预定义的, 固定大小的字节数组.

如果你请求一个任意数量内存, 你可能得到稍微多于你请求的, 至多是 2 倍数量. 同样, 程序员应当记住kmalloc 能够处理的最小分配是 32 或者 64 字节, 依赖系统的体系所使用的页大小. kmalloc 能够分配的内存块的大小有一个上限. 这个限制随着体系和内核配置选项而变化. 如果你的代码是要完全可移植, 它不能指望可以分配任何大于 128 KB. 如果你需要多于几个 KB, 但是, 有个比 kmalloc 更好的方法来获得内存。在设备驱动程序或者内核模块中动态开辟内存,不是用malloc,而是kmalloc
,vmalloc,或者用get_free_pages直接申请页。释放内存用的是kfree,vfree,或free_pages. kmalloc函数返回的是虚拟地址(线性地址). kmalloc特殊之处在于它分配的内存是物理上连续的,这对于要进行DMA的设备十分重要. 而用vmalloc分配的内存只是线性地址连续,物理地址不一定连续,不能直接用于DMA.

注意kmalloc最大只能开辟128k-16,16个字节是被页描述符结构占用了。kmalloc用法参见khg.

内存映射的I/O口,寄存器或者是硬件设备的RAM(如显存)一般占用F0000000以上的地址空间。在驱动程序中不能直接访问,要通过kernel函数vremap获得重新映射以后的地址。

另外,很多硬件需要一块比较大的连续内存用作DMA传送。这块内存需要一直驻留在内存,不能被交换到文件中去。但是kmalloc最多只能开辟大小为32XPAGE_SIZE的内存,一般的PAGE_SIZE=4kB,也就是128kB的大小的内存。

3.kmalloc和vmalloc的区别

• vmalloc()与 kmalloc()都可用于分配内存

• kmalloc()分配的内存处于3GB~high_memory之 间,这段内核空间与物理内存的映射一一对应

•vmalloc()分配的内存在 VMALLOC_START~4GB之间,这段非连续内 存区映射到物理内存也可能是非连续的

• 在内核空间中调用kmalloc()分配连续物理空间,而调用vmalloc()分配非物理连续空间。

• 把kmalloc()所分配内核空间中的地址称为内核逻辑地址

• 把vmalloc()分配的内核空间中的地址称 为内核虚拟地址

• vmalloc()在分配过程中须更新内核页表

总结:

1.kmalloc和vmalloc分配的是内核的内存,malloc分配的是用户的内存

2.kmalloc保证分配的内存在物理上是连续的,kmalloc()分配的内存在0xBFFFFFFF-0xFFFFFFFF以上的内存中,driver一般是用它来完成对DS的分配,更适合于类似设备驱动的程序来使用;

3.vmalloc保证的是在虚拟地址空间上的连续,vmalloc()则是位于物理地址非连续,虚地址连续区,起始位置由VMALLOL_START来决定,一般作为交换区、模块的分配。

3.kmalloc能分配的大小有限,vmalloc和malloc能分配的大小相对较大(因为vmalloc还可以处理交换空间)。

4.内存只有在要被DMA访问的时候才需要物理上连续,vmalloc比kmalloc要慢

5.vmalloc使用的正确场合是分配一大块,连续的,只在软件中存在的,用于缓冲的内存区域。不能在微处理器之外使用。

6.vmalloc 中调用了 kmalloc (GFP—KERNEL),因此也不能应用于原子上下文。

7.kmalloc和 kfree管理内核段内分配的内存,这是真实地址已知的实际物理内存块。

8.vmalloc对应于vfree,分配连续的虚拟内存,但是物理上不一定连续。

9.kmalloc分配内存是基于slab,因此slab的一些特性包括着色,对齐等都具备,性能较好。物理地址和逻辑地址都是连续的

三.module_param

1.为什么引入

在用户态下编程可以通过main()来传递命令行参数,而编写一个内核模块则可通过module_param()来传递命令行参数.

2.module_param宏是Linux 2.6内核中新增的,该宏被定义在include/linux/moduleparam.h文件中,具体定义如下:

[cpp] view
plaincopy

/* Helper functions: type is byte, short, ushort, int, uint, long,

ulong,charp, bool or invbool, or XXX if you define param_get_XXX,

param_set_XXX and param_check_XXX.

*/

#define module_param_named(name, value, type,perm)

param_check_##type(name,&(value));

module_param_call(name, param_set_##type, param_get_##type, &value,perm);

__MODULE_PARM_TYPE(name, #type)

#define module_param(name, type,perm)

module_param_named(name, name, type, perm)

由此可知module_param的实现是通过module_param_named(name, name, type, perm)的。

3.module_param使用了3个参数:变量名,它的类型,以及一个权限掩码用来做一个辅助的sysfs入口。

这个宏定义应当放在任何函数之外,典型地是出现在源文件的前面。

eg:static char *whom="world"

static int tige=1;

module_param(tiger,int,S_IRUGO);

module_param(whom,charp,S_IRUGO);

4.模块参数支持许多类型:

bool

invbool

一个布尔型(true 或者 false)值(相关的变量应当是 int 类型). invbool 类型颠倒了值, 所以真值变成 false, 反之亦然.

charp:一个字符指针值. 内存为用户提供的字串分配, 指针因此设置.

int

long

short

uint

ulong

ushort

基本的变长整型值.以 u 开头的是无符号值.

5.数组参数,用逗号间隔的列表提供的值, 模块加载者也支持。

声明一个数组参数,使用:

module_param_array(name,type,num,perm);

这里 name是你的数组的名子(也是参数名),

type是数组元素的类型,

num是一个整型变量,

perm是通常的权限值.

如果数组参数在加载时设置,num 被设置成提供的数的个数. 模块加载者拒绝比数组能放下的多的值.

Tiger-John说明:

perm参数的作用是什么?

最后的module_param 字段是一个权限值,表示此参数在sysfs文件系统中所对应的文件节点的属性。你应当使用 <linux/stat.h>中定义的值. 这个值控制谁可以存取这些模块参数在 sysfs 中的表示.当perm为0时,表示此参数不存在 sysfs文件系统下对应的文件节点。 否则,模块被加载后,在/sys/module/ 目录下将出现以此模块名命名的目录, 带有给定的权限.。

权限在include/linux/stat.h中有定义

比如:

#defineS_IRWXU 00700

#defineS_IRUSR 00400

#defineS_IWUSR 00200

#defineS_IXUSR 00100

#defineS_IRWXG 00070

#defineS_IRGRP 00040

#defineS_IWGRP 00020

#defineS_IXGRP 00010

#defineS_IRWXO 00007

#defineS_IROTH 00004

#defineS_IWOTH 00002

#defineS_IXOTH 00001

使用S_IRUGO 参数可以被所有人读取, 但是不能改变; S_IRUGO|S_IWUSR 允许 root 来改变参数. 注意, 如果一个参数被 sysfs修改, 你的模块看到的参数值也改变了, 但是你的模块没有任何其他的通知. 你应当不要使模块参数可写, 除非你准备好检测这个改变并且因而作出反应.

四.file文件结构

struct file, 定义于<linux/fs.h>, 是设备驱动中第二个最重要的数据结构. 注意 file 与用户空间程序的 FILE 指针没有任何关系. 一个 FILE定义在 C 库中, 从不出现在内核代码中. 一个 struct file, 另一方面, 是一个内核结构, 从不出现在用户程序中.

文件结构代表一个打开的文件.(它不特定给设备驱动; 系统中每个打开的文件有一个关联的 struct file 在内核空间). 它由内核在 open 时创建,并传递给在文件上操作的任何函数, 直到最后的关闭. 在文件的所有实例都关闭后, 内核释放这个数据结构.

在内核源码中, struct file的指针常常称为 file 或者 filp("file pointer"). 我们将一直称这个指针为 filp 以避免和结构自身混淆.因此, file 指的是结构, 而 filp 是结构指针.

struct file 的最重要成员在这展示.如同在前一节, 第一次阅读可以跳过这个列表. 但是, 在本章后面, 当我们面对一些真实 C 代码时, 我们将更详细讨论这些成员.

mode_tf_mode;

文件模式确定文件是可读的或者是可写的(或者都是),通过位 FMODE_READ 和 FMODE_WRITE. 你可能想在你的 open 或者 ioctl 函数中检查这个成员的读写许可,但是你不需要检查读写许可, 因为内核在调用你的方法之前检查. 当文件还没有为那种存取而打开时读或写的企图被拒绝, 驱动甚至不知道这个情况.

loff_tf_pos;

当前读写位置.loff_t 在所有平台都是 64 位( 在 gcc 术语里是 long long ). 驱动可以读这个值, 如果它需要知道文件中的当前位置,但是正常地不应该改变它; 读和写应当使用它们作为最后参数而收到的指针来更新一个位置, 代替直接作用于 filp->f_pos. 这个规则的一个例外是在llseek 方法中, 它的目的就是改变文件位置.

unsignedint f_flags;

这些是文件标志,例如 O_RDONLY, O_NONBLOCK, 和 O_SYNC. 驱动应当检查 O_NONBLOCK 标志来看是否是请求非阻塞操作(我们在第一章的"阻塞和非阻塞操作"一节中讨论非阻塞 I/O ); 其他标志很少使用. 特别地, 应当检查读/写许可, 使用 f_mode而不是 f_flags. 所有的标志在头文件 <linux/fcntl.h> 中定义.

structfile_operations *f_op;

和文件关联的操作.内核安排指针作为它的 open 实现的一部分, 接着读取它当它需要分派任何的操作时. filp->f_op 中的值从不由内核保存为后面的引用;这意味着你可改变你的文件关联的文件操作, 在你返回调用者之后新方法会起作用. 例如, 关联到主编号 1 (/dev/null, /dev/zero, 等等)的open 代码根据打开的次编号来替代 filp->f_op 中的操作. 这个做法允许实现几种行为, 在同一个主编号下而不必在每个系统调用中引入开销.替换文件操作的能力是面向对象编程的"方法重载"的内核对等体.

void*private_data;

open系统调用设置这个指针为 NULL, 在为驱动调用 open 方法之前. 你可自由使用这个成员或者忽略它; 你可以使用这个成员来指向分配的数据,但是接着你必须记住在内核销毁文件结构之前, 在 release 方法中释放那个内存. private_data 是一个有用的资源, 在系统调用间保留状态信息,我们大部分例子模块都使用它.

structdentry *f_dentry;

关联到文件的目录入口(dentry )结构. 设备驱动编写者正常地不需要关心 dentry 结构, 除了作为 filp->f_dentry->d_inode 存取inode 结构.

真实结构有多几个成员,但是它们对设备驱动没有用处. 我们可以安全地忽略这些成员, 因为驱动从不创建文件结构; 它们真实存取别处创建的结构.

五.inode结构

inode 结构由内核在内部用来表示文件. 因此, 它和代表打开文件描述符的文件结构是不同的.可能有代表单个文件的多个打开描述符的许多文件结构, 但是它们都指向一个单个 inode 结构.

inode 结构包含大量关于文件的信息. 作为一个通用的规则, 这个结构只有 2 个成员对于编写驱动代码有用:

dev_ti_rdev;

对于代表设备文件的节点,这个成员包含实际的设备编号.

structcdev *i_cdev;

structcdev 是内核的内部结构, 代表字符设备; 这个成员包含一个指针, 指向这个结构, 当节点指的是一个字符设备文件时.

i_rdev 类型在 2.5 开发系列中改变了, 破坏了大量的驱动. 作为一个鼓励更可移植编程的方法, 内核开发者已经增加了 2 个宏,可用来从一个 inode 中获取主次编号:

unsigned int iminor(struct inode *inode);

unsigned int imajor(struct inode *inode);

为了不要被下一次改动抓住, 应当使用这些宏代替直接操作 i_rdev.

六.container_of()

container_of()的作用是通过结构体成员的指针找到对应

结构体的指针,这个技巧在Linux内核编程中十分常用。在container_of

(inode->i_cdev,structglobalmem_dev,cdev)语句中,传给container_of()的第1个参数是结

构体成员的指针,第2个参数为整个结构体的类型,第3 个参数为传入的第1 个参数即

结构体成员的类型,container_of()返回值为整个结构体的指针。

七.ioctl函数

首先说明在2.6.36以后ioctl函数已经不再存在了,而是用unlocked_ioctl和compat_ioctl两个函数实现以前版本的ioctl函数。同时在参数方面也发生了一定程度的改变,去除了原来ioctl中的struct inode参数,同时改变了返回值。

但是驱动设计过程中存在的问题变化并不是很大,同样在应用程序设计中我们还是采用ioctl实现访问,而并不是unlocked_ioctl函数,因此我们还可以称之为ioctl函数的实现。

ioctl函数的实现主要是用来实现具体的硬件控制,采用相应的命令控制硬件的具体操作,这样就能使得硬件的操作不再是单调的读写操作。使得硬件的使用更加的方便。

ioctl函数实现主要包括两个部分,首先是命令的定义,然后才是ioctl函数的实现,命令的定义是采用一定的规则。

ioctl的命令主要用于应用程序通过该命令操作具体的硬件设备,实现具体的操作,在驱动中主要是对命令进行解析,通过switch-case语句实现不同命令的控制,进而实现不同的硬件操作。

ioctl函数的命令定义方法:

int (*unlocked_ioctl)(struct file*filp,unsigned int cmd,unsigned long arg)

虽然其中没有指针的参数,但是通常采用arg传递指针参数。cmd是一个命令。每一个命令由一个整形数据构成(32bits),将一个命令分成四部分,每一部分实现具体的配置,设备类型(幻数)8bits,方向2bits,序号8bits,数据大小13/14bits。命令的实现实质上就是通过简单的移位操作,将各个部分组合起来而已。

一个命令的分布的大概情况如下:

|---方向位(31-30)|----数据长度(29-16)----------------|---------设备类型(15-8)------|----------序号(7-0)----------|

|----------------------------------------------------------------------------------------------------------------------------------------|

其中方向位主要是表示对设备的操作,比如读设备,写设备等操作以及读写设备等都具有一定的方向,2个bits只有4种方向。

数据长度表示每一次操作(读、写)数据的大小,一般而已每一个命令对应的数据大小都是一个固定的值,不会经常改变,14bits说明可以选择的数据长度最大为16k。

设备类型类似于主设备号(由于8bits,刚好组成一个字节,因此经常采用字符作为幻数,表示某一类设备的命令),用来区别不同的命令类型,也就是特定的设备类型对应特定的设备。序号主要是这一类命令中的具体某一个,类似于次设备号(256个命令),也就是一个设备支持的命令多达256个。

同时在内核中也存在具体的宏用来定义命令以及解析命令。

但是大部分的宏都只是定义具体的方向,其他的都需要设计者定义。

主要的宏如下:

[cpp] view
plaincopy

#include<linux/ioctl.h>

_IO(type,nr) 表示定义一个没有方向的命令,

_IOR(type,nr,size) 表示定义一个类型为type,序号为nr,数据大小为size的读命令

_IOW(type,nr,size) 表示定义一个类型为type,序号为nr,数据大小为size的写命令

_IOWR(type,nr,size) 表示定义一个类型为type,序号为nr,数据大小为size的写读命令

通常的type可采用某一个字母或者数字作为设备命令类型。

是实际运用中通常采用如下的方法定义一个具体的命令:

[cpp] view
plaincopy

//头文件

#include<linux/ioctl.h>

/*定义一系列的命令*/

/*幻数,主要用于表示类型*/

#define MAGIC_NUM 'k'

/*打印命令*/

#define MEMDEV_PRINTF _IO(MAGIC_NUM,1)

/*从设备读一个int数据*/

#define MEMDEV_READ _IOR(MAGIC_NUM,2,int)

/*往设备写一个int数据*/

#define MEMDEV_WRITE _IOW(MAGIC_NUM,3,int)

/*最大的序列号*/

#define MEM_MAX_CMD 3

还有对命令进行解析的宏,用来确定具体命令的四个部分(方向,大小,类型,序号)具体如下所示:

[cpp] view
plaincopy

/*确定命令的方向*/

_IOC_DIR(nr)

/*确定命令的类型*/

_IOC_TYPE(nr)

/*确定命令的序号*/

_IOC_NR(nr)

/*确定命令的大小*/

_IOC_SIZE(nr)

上面的几个宏可以用来命令,实现命令正确性的检查。

ioctl的实现过程主要包括如下的过程:

1、命令的检测

2、指针参数的检测

3、命令的控制switch-case语句

1、命令的检测主要包括类型的检查,数据大小,序号的检测,通过结合上面的命令解析宏可以快速的确定。

[cpp] view
plaincopy

/*检查类型,幻数是否正确*/

if(_IOC_TYPE(cmd)!=MAGIC_NUM)

return -EINVAL;

/*检测命令序号是否大于允许的最大序号*/

if(_IOC_NR(cmd)> MEM_MAX_CMD)

return -EINVAL;

2、主要是指针参数的检测。指针参数主要是因为内核空间和用户空间的差异性导致的,因此需要来自用户空间指针的有效性。使用copy_from_user,copy_to_user,get_user,put_user之类的函数时,由于函数会实现指针参量的检测,因此可以省略,但是采用__get_user(),__put_user()之类的函数时一定要进行检测。具体的检测方法如下所示:

[cpp] view
plaincopy

if(_IOC_DIR(cmd) & _IOC_READ)

err = !access_ok(VERIFY_WRITE,(void *)args,_IOC_SIZE(cmd));

else if(_IOC_DIR(cmd) & _IOC_WRITE)

err = !access_ok(VERIFY_READ,(void *)args,_IOC_SIZE(cmd));

if(err)/*返回错误*/

return -EFAULT;

当方向是读时,说明是从设备读数据到用户空间,因此要检测用户空间的指针是否可写,采用VERIFY_WRITE,而当方向是写时,说明是往设备中写数据,因此需要检测用户空间中的指针的可读性VERIFY_READ。检查通常采用access_ok()实现检测,第一个参数为读写,第二个为检测的指针,第三个为数据的大小。

3、命名的控制:

命令的控制主要是采用switch和case相结合实现的,这于window编程中的检测各种消息的实现方式是相同的。

[cpp] view
plaincopy

/*根据命令执行相应的操作*/

switch(cmd)

{

case MEMDEV_PRINTF:

printk("<--------CMD MEMDEV_PRINTF Done------------>\n\n");

...

break;

case MEMDEV_READ:

ioarg = &mem_devp->data;

...

ret = __put_user(ioarg,(int *)args);

ioarg = 0;

...

break;

case MEMDEV_WRITE:

...

ret = __get_user(ioarg,(int *)args);

printk("<--------CMD MEMDEV_WRITE Done ioarg = %d--------->\n\n",ioarg);

ioarg = 0;

...

break;

default:

ret = -EINVAL;

printk("<-------INVAL CMD--------->\n\n");

break;

}

这只是基本的框架结构,实际中根据具体的情况进行修改。这样就实现了基本的命令控制。

文件操作支持的集合如下:

[cpp] view
plaincopy

/*添加该模块的基本文件操作支持*/

static const struct file_operations mem_fops =

{

/*结尾不是分号,注意其中的差别*/

.owner = THIS_MODULE,

.llseek = mem_llseek,

.read = mem_read,

.write = mem_write,

.open = mem_open,

.release = mem_release,

/*添加新的操作支持*/

.unlocked_ioctl = mem_ioctl,

};

需要注意不是ioctl,而是unlocked_ioctl。

参考链接:

http://blog.csdn.net/tigerjb/article/details/6412881

/article/9048447.html

http://blog.chinaunix.net/space.php?uid=20937170&do=blog&id=3033633





以下是自己已编译OK 的代码

Description:
This is a simple Character Device Drives with globalmem.

Author/Created Date:
Lumi-liu, Nov19'14

Modification History:

Note:

=====================================*/
#include <linux/kernel.h>
#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 <asm/io.h>
#include <linux/kdev_t.h>
#include <asm/uaccess.h>
#include <linux/device.h>
#include <linux/slab.h>

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Lumi-liu");

#define DEVICE_NAME "globalmem"
#define GLOBALMEM_SIZE 0x1000
#define MEM_CLEAR 0x1
//#define GLOBALMEM_MAJOR 255  //指定主设备号
#define GLOBALMEM_MAJOR 0  //动态申请主设备号

static int globalmem_major = GLOBALMEM_MAJOR;

static struct class *globalmem_class;

//设备结构体
struct globalmem_dev
{
struct cdev cdev;
unsigned char mem[GLOBALMEM_SIZE];
};

//设备结构体指针
struct globalmem_dev *globalmem_devp;

//设备文件打开
int globalmem_open(struct inode *inode,struct file *filp)
{
filp->private_data = globalmem_devp;
return 0;
}

//设备文件释放
int globalmem_release(struct inode *inode, struct file *filp)
{
return 0;
}

//设备文件控制
static long globalmem_ioctl(/*struct inode *inodep, */struct file *filp, unsigned int cmd, unsigned long arg)
{
struct globalmem_dev *dev = filp->private_data;

switch (cmd)
{
case MEM_CLEAR:
memset(dev->mem,0,GLOBALMEM_SIZE);
printk(KERN_INFO "globalmem is set to zero\n");
break;

default:
return -EINVAL;
}
return 0;
}

//设备文件读
static ssize_t globalmem_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 globalmem_dev *dev = filp->private_data;

if (p > GLOBALMEM_SIZE)
return count ? -ENXIO: 0;
//    return 0;
if (count > GLOBALMEM_SIZE - p)
count = GLOBALMEM_SIZE - p;

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

printk(KERN_INFO "read %u bytes(s) from %lu\n",count,p);
}

return ret;
}

//设备文件写
static ssize_t globalmem_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 globalmem_dev *dev = filp->private_data;

if (p > GLOBALMEM_SIZE)
return count ? -ENXIO: 0;
//    return 0;
if (count > GLOBALMEM_SIZE - p)
count = GLOBALMEM_SIZE - p;

if (copy_from_user(dev->mem + p, buf, count))
{
ret = -EFAULT;
}
else
{
*ppos += count;
ret = count;

printk(KERN_INFO "written %u bytes(s) from %lu\n",count, p);
}
return ret;
}

/*
static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
{
loff_t ret = 0;
switch (orig)
{
case 0:
if (offset < 0)
{
ret = -EINVAL;
break;
}
if ((unsigned int)offset > GLOBALMEM_SIZE)
{
ret = -EINVAL;
break;
}
filp->f_pos = (unsigned int)offset;
ret = filp->f_pos;
break;
case 1:
if ((filp->f_pos + offset) > GLOBALMEM_SIZE)
{
ret = -EINVAL;
break;
}
if ((filp->f_pos + offset) < 0)
{
ret = -EINVAL;
break;
}
filp->f_pos += offset;
ret = filp->f_pos;
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
*/

//文件操作结构体
static const struct file_operations globalmem_fops =
{
.owner = THIS_MODULE,
//    .llseek = globalmem_llseek,
.read = globalmem_read,
.write = globalmem_write,
//    .ioctl = globalmem_ioctl,
.compat_ioctl = globalmem_ioctl,
//    .unlocked_ioctl = globalmem_ioctl,
.open = globalmem_open,
.release = globalmem_release,
};

//初始化并注册设备
static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)
{
int err;
int devno = MKDEV(globalmem_major,index);

cdev_init(&dev->cdev, &globalmem_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &globalmem_fops;
err = cdev_add(&dev->cdev, devno, 1);
if (err)
printk(KERN_NOTICE "Error %d adding GLOBALMEM %d",err, index);
}

//设备模块加载
int globalmem_init(void)
{
int result;

dev_t devno = MKDEV(globalmem_major, 0);

if (globalmem_major)
result = register_chrdev_region(devno, 1, "globalmem");
else
{
result = alloc_chrdev_region(&devno, 0, 1, "globalmem");
globalmem_major = MAJOR(devno);
}
if (result < 0)
return result;
//动态申请结构体内存
globalmem_devp = kmalloc(sizeof(struct globalmem_dev), GFP_KERNEL);
if (!globalmem_devp)
{
result = -ENOMEM;
goto fail_malloc;
}
memset(globalmem_devp, 0, sizeof(struct globalmem_dev));

globalmem_setup_cdev(globalmem_devp, 0);

globalmem_class =class_create(THIS_MODULE, "globalmem");
if( IS_ERR(globalmem_class) )
{
printk(KERN_NOTICE "creatglobalmem_class failed!");
return -1;
}

//创建一个设备节点,节点名字为globalmem
device_create(globalmem_class, NULL,MKDEV(globalmem_major, 0),
NULL, "globalmem");
printk(KERN_NOTICE "globalmem initialized!");

return 0;

fail_malloc:
unregister_chrdev_region(devno, 1);
return result;
}

void globalmem_exit(void)
{
dev_t devno = MKDEV(globalmem_major, 0);

device_destroy(globalmem_class,devno);
class_destroy(globalmem_class);

//kfree(globalmem_devp);
cdev_del(&globalmem_devp->cdev);
kfree(globalmem_devp);
unregister_chrdev_region(devno,/*MKDEV(globalmem_major, 0),*/ 1);
printk(KERN_NOTICE "globalmem goodbye!");
}

module_param(globalmem_major, int, S_IRUGO);

module_init(globalmem_init);
module_exit(globalmem_exit);

Makefile文件

obj-m := globalmem.o
KERNEL_DIR := /lib/modules/$(shell uname -r)/build
#PWD := $(shell pwd)
all:
make -C $(KERNEL_DIR) SUBDIRS=$(shell pwd) modules
clean:
rm -rf *.o *.ko *.mod.c *.mod.o *.symvers *.order .*.ko.cmd .*.o.cmd .*.mod.o.cmd .*.swp .tmp_versions
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: