linux设备驱动读取失败,Linux设备驱动总结 【字符型设备的操作open、close、read、write 】...

struct

file_operations {

struct module *owner;

loff_t (*llseek) (struct file *, loff_t, int);

ssize_t (*read) (struct file *, char __user *, size_t, loff_t

*);

ssize_t (*write) (struct file *, const char __user *, size_t,

loff_t *);

ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned

long, loff_t);

ssize_t (*aio_write) (struct kiocb *, const struct iovec *,

unsigned long, loff_t);

int (*readdir) (struct file *, void *,

filldir_t);

unsigned int (*poll) (struct file *, struct poll_table_struct

*);

long (*unlocked_ioctl) (struct file *, unsigned int, unsigned

long);

long (*compat_ioctl) (struct file *, unsigned int, unsigned

long);

int (*mmap) (struct file *, struct vm_area_struct

*);

int (*open) (struct inode *, struct file *);

int (*flush) (struct file *, fl_owner_t id);

int (*release) (struct inode *, struct file *);

int (*fsync) (struct file *, int datasync);

int (*aio_fsync) (struct kiocb *, int datasync);

int (*fasync) (int, struct file *, int);

int (*lock) (struct file *, int, struct file_lock

*);

ssize_t (*sendpage) (struct file *, struct page *, int, size_t,

loff_t *, int);

unsigned long (*get_unmapped_area)(struct file *, unsigned long,

unsigned long, unsigned long, unsigned long);

int (*check_flags)(int);

int (*flock) (struct file *, int, struct file_lock

*);

ssize_t (*splice_write)(struct pipe_inode_info *, struct file *,

loff_t *, size_t, unsigned int);

ssize_t (*splice_read)(struct file *, loff_t *, struct

pipe_inode_info *, size_t, unsigned int);

int (*setlease)(struct file *, long, struct file_lock

**);

};

我们会发现,上面的函数很多都跟系统编程的函数很相似,因为这里的函数是跟系统编程的函数对应的,如在应用层调用函数open来操作设备文件,内核就会调用文件操作结构体中的成员open来进行相应的操作。

上面的函数我也只是用过一小部分,下面先写一下打开和关闭设备的函数

int

(*open) (struct inode *, struct file *);

在操作设备前必须先调用open函数打开文件,可以干一些需要的初始化操作。当然,如果不实现这个函数的话,驱动会默认设备的打开永远成功。打开成功时open返回0。

int

(*release) (struct inode *, struct file *);

当设备文件被关闭时内核会调用这个操作,当然这也可以不实现,函数默认为NULL。关闭设备永远成功。

上面的函数中的的两个参数现在还没需要用到,迟点用到了会解释这两个结构体的用途。所以,下面的程序的打开和关闭并没有做实质的操作,只是想验证一下,注册设备可以调用filr_opreations中定义的函数。

下面我们以文件驱动函数为例说明:

#include

#include

#include

#include

#include

#include

#include

#include

#include

MODULE_LICENSE("GPL");//

遵循GPL 协议

int devno_major=0; //

主设备号

int devno_minor=1;//

次设备号

//声明devno_major为模块参数,允许用户指定主设备号

module_param(devno_major,

int, 0660);

struct cdev

*pdev = NULL; //指向字符设备结构体

struct

device *dev = NULL; //指向一个设备

struct

class *cls = NULL; //指向设备类的结构体,表示一类设备

int my_open

(struct inode *_inode, struct file * _file)

{

//打开设备的操作,返回0表示成功,其他值表示失败

printk(KERN_INFO "open device is successful!\n");

return

0;

}

int

my_close (struct inode *_inode, struct file

*_file)

{

//关闭设备的操作,返回0表示成功,其他值表示失败

printk(KERN_INFO "close device is sucessful!\n");

return 0;

}

ssize_t

my_read (struct file *_file, char __user *buf, size_t count, loff_t

*offset)

{

int ret;//memcpy(buf,

"test_data", count);用户buf指针为null ,内核崩溃

if (copy_to_user(buf,

"test_data", count)) // 从内核复制"test_data"到用户态

{

ret = - EFAULT;

}

else

{

ret = count;

printk(KERN_INFO"buf is

[%s]\n", buf);

}

return ret;

//返回实际读取的字节数或错误号

}

ssize_t

my_write (struct file *_file, char __user *buf, size_t count,

loff_t *offset)

{

char

kbuf[25];

int ret;//memcpy(kbuf, buf,

count);

if (copy_from_user(kbuf, buf,

count))

{

ret = - EFAULT;

}

else

{

ret = count;

printk(KERN_INFO"kbuf is

[%s]\n", kbuf);

}

return ret; //

返回成功写入的字节数或错误号

}

//第二个参数cmd为命令号,具体数值及含义由驱动开发者定义

//第三个参数arg,为相应的命令的参数,这个不管你是什么类型的,反正我内核都用unsigned

long

#define GET_AVAIL_LEN

_IOR('M', 1, int)

//带一个参数,一个int指针,内核把缓冲区中实际可读的字节数,写入到该指针中去

long my_ioctl

(struct file * _file,  unsigned

int cmd , unsigned long arg)

{

printk(KERN_INFO "cmd = %d arg = %lu\n", cmd, arg);

switch(cmd)

{

case GET_AVAIL_LEN:

{

int __user *ptr = (int

__user*)arg;

put_user(114, ptr);

}

default:

break;

}

return 0;

}

struct

file_operations myops = {  .open = my_open,

.release = my_close,

.read=my_read, .write=my_write , .unlocked_ioctl = my_ioctl

};

static int __init

char_driver_init(viod)

{

int ret;

dev_t

devno;

//设备号

if (devno_major >

0)

{

//用户指定了主设备号,用静态方式申请设备号

devno = MKDEV(devno_major,

devno_minor);

ret =

register_chrdev_region(devno, //要申请的设备号

1,  //申请的数量

"char_test"

//驱动的名字,随便写,只要你开心就好

);

}

else

{ //用户没有指定主设备号,用动态方式申请设备号

ret =

alloc_chrdev_region(&devno, //一个地址,用来保存成功申请到的第一个设备号

devno_minor, //指定第一个次设备号

1, //申请的数量

"char_driver_test"

//驱动的名字,....

);

}

if (ret != 0)

{

printk(KERN_ERR "devno

register failed\n");

return -1;

}

devno_major = MAJOR(devno);

devno_minor = MINOR(devno);

printk(KERN_INFO "devno_major

: %d  devno_minor = %d\n", devno_major,

devno_minor);

pdev = cdev_alloc();

//unlikely不像,不太可能的,

// likely像,有可能的, (C

预取指)

// unlikey/likely (表达式),

是一个编译器的修饰词

//unlikely ->

后面那玩意(括号里面那个表达式)  不太可能成立

// likely ->

后面那玩意(括号里面那个表达式)很有可能成立

// =>

提高预取指的命中率

if (unlikely( IS_ERR(pdev)

))

//在linux内核中用宏IS_ERR来判断一个指针是否有错误,

为什么不用 == NULL

{

//内核指针除了表示是否有错误外,还把错误代码给返回了

//

也如果一个内核指针的值3, != NULL,但是也表示出错,而且出错代码为3

printk(KERN_ERR "cdev_alloc

failed\n");

//注销设备号

unregister_chrdev_region(devno,

1);

return -1;

}

//指定cdev字符设备的文件操作的接口函数

cdev_init(pdev, &myops);

cdev_add(pdev, devno,

1);

cls =

class_create(THIS_MODULE, "char_driver_test");

if ( unlikely (IS_ERR(cls)) )

{

printk(KERN_ERR "failed to

class_create\n");

cdev_del(pdev); //注销设备号

unregister_chrdev_region(devno,

1);

return -1;

}

return 0;

dev = device_create(cls,

NULL, devno, NULL, "test%d",devno_minor);

if (unlikely (IS_ERR(dev)))

{

printk(KERN_ERR "failed to

device_create\n");

cdev_del(pdev); //注销设备号

unregister_chrdev_region(devno,

1);

class_destroy(cls);

//销毁一个设备类

return -1;

}

}

static void __exit

char_driver_exit(void)

{

dev_t devno =

MKDEV(devno_major, devno_minor);

cdev_del(pdev);

//注销设备号

unregister_chrdev_region(devno,

1);

}

module_init(char_driver_init);

module_exit(char_driver_exit);

在这里说明下,用file_operations中的read和write模拟两件事:

1)从内核态通过read函数读取数据到用户态。

2)从用户态通过write函数读取数据到内核态。

驱动函数:ssize_t (*read) (struct file *, char

__user *, size_t, loff_t *);

与用户层的read对应:ssize_t

read(int fd, void *buf, size_t count);

用法:

从设备中读取数据,当用户层调用函数read时,对应的,内核驱动就会调用这个函数。

参数:

struct

file:file结构体,现在暂时不用,可以先不传参。

char

__user:只看到__user就知道这是从用户态的指针,通过这个指针往用户态传数据。这是对应用户层的read函数的第二个参数void

*buf。

size_t:写内核的人就是闲着喜欢编一些奇怪的类型,其实这只是unsigned

int。对应应用层的read函数的第三个参数。

loff_t:这是用于存放文件的偏移量的,回想一下系统编程时,读写文件的操作都会使偏移量往后移。不过待会的代码先不实现,迟点会说。

返回值:

当返回正数时,内核会把值传给应用程序的返回值。一般的,调用成功会返回成功读取的字节数。

如果返回负数,内核就会认为这是错误,应用程序返回-1。

ssize_t (*write) (struct file *, const char __user

*, size_t, loff_t *);

与用户层的write对应:ssize_t

write(int fd, const void *buf, size_t

count);

用法:往设备写入数据,当用户层调用函数write时,对应的,内核驱动就会调用这个函数。

参数:

struct

file:file结构体,现在暂时不用,可以先不传参。

char

__user:只看到__user就知道这是从用户态的指针,通过这个指针读取用户态的数据。这是对应用户层的write函数的第二个参数const void

*buf。

size_t:写内核的人就是闲着喜欢编一些奇怪的类型,其实这只是unsigned

int。对应用户层的write函数的第三个参数count。

loff_t:这是用于存放文件的偏移量的,回想一下系统编程时,读写文件的操作都会使偏移量往后移。不过待会的代码先不实现,迟点会说。

返回值:

当返回正数时,内核会把值传给应用程序的返回值。一般的,调用成功会返回成功读取的字节数。

如果返回负数,内核就会认为这是错误,应用程序返回-1。

当然和现实的read、write有点区别。我只是实现了参数在内核与用户态之间传递,偏移量、存进内存等我都没有实现。

根据上面open、close、read、write四个操作,下面来画一个拉风的时序图。上面的read、write函数的数据是我在函数里面瞎编的,根本不是从硬件(如寄存器)读取出来的。我就先想象一下这是硬件上的数据。(当然这是指一个基本的模型,内核的操作比这个复杂)。

注:箭头方向是从调用的一方指向受作用的一方。

a4c26d1e5885305701be709a3d33442f.png

除了读写能力,大部分驱动程序还能执行各类型的硬件控制,比如,请求设备锁门、弹出介质、报告错误信息、改变波特率或执行自破坏等等。这些操作通常ioctl方法支持。在用户空间,ioctl系统调用具有如下原型:

int ioctl(int fd,unsigned

long cmd,...);

驱动程序的ioctl方法原型:

int (*ioctl)(struct inode

*inode,struct file *filp,unsigned int cmd,unsigned long

arg);

其中参数cmd由用户空间不经修改地传递给驱动程序,可选的arg参数则无论用户程序使用的是指针还是整数值,它都以unsigned

long的形式传递给驱动程序。如果调用程序没有传递第三个参数,那么驱动程序所接收的arg参数处在未定义状态。如果ioctl传递一个非法参数,编译器是无法报警的,这样,相关联的程序错误就很难发现。

为了防止对错误的设备使用正确的命令,命令号应该在系统范围内唯一。从include/asm/ioctl.h头文件中,我们可以得出,cmd为一个32位的无符号整数,被划分成4个段,具体表示如下:

a4c26d1e5885305701be709a3d33442f.png

使用ioctl的可选arg参数时,如果传递的是一个整数,它可以直接使用。如果是一个指针,就必须小心。当用一个指针引用用户空间,

我们必须确保用户地址是合法的,可通过函数access_ok验证地址(而不是传递参数),该函数中声明:

int access_ok(int type, const void

*addr, unsigned long size);

第一个参数应当是

VERIFY_READ或VERIFY_WRITE,取决于要执行的动作是读取还是写入用户空间内存区;addr

参数为用户空间地址,size 为字节数。如果在指定地址处既要读取又要写入,则应该用VERIFY_WRITE。access_ok

返回一个布尔值: 1 是成功(存取没问题)和 0

是失败(存取有问题)。如果它返回假,驱动应当返回-EFAULT给调用者。

对于access_ok,有两点值得注意:

1)access_ok并没有完成验证内存的全部工作,而只检查所引用内存是否位于进程有对应访问权限的区域内,特别是要确保访问地址没有指向内核空间。2)大部分驱动代码不需要真正调用

access_ok,而直接使用put_user(datum, ptr)和get_user(local,

ptr),它们带有校验的功能,确保进程能够写入给定的内存地址,成功时返回 0, 并且在错误时返回

-EFAULT。

blog_140bf5a310102x22n.html

blog_140bf5a310102x22n.html

上面讲的东西很少:

1)file operations的用途

2)copy_to_user和copy_from_user的用法

—copy_from_user

Name

copy_from_user

--  Copy a block of data from user

space.

Synopsis

unsigned long

copy_from_user (void * to,

const void

__user * from, unsigned long n);

Arguments

to

:Destination address, in kernel

space.

from:Source

address, in user space.

n:Number of

bytes to copy.

Context

User context

only. This function may sleep.

Description

Copy data from

user space to kernel space.

Returns number

of bytes that could not be copied. On success, this will be zero.If

some data could not be copied, this function will pad the copied

data to the requested size using zero bytes.

——copy_to_user

Name

copy_to_user --

Copy a block of data into user

space.

Synopsis

unsigned long

copy_to_user (void __user * to,

const void *

from,

unsigned long

n);

Arguments

to:Destination

address, in user space.

from:Source

address, in kernel space.

n:Number of bytes

to copy.

Context

User context

only. This function may sleep.

Description

Copy data from

kernel space to user space.

Returns number of

bytes that could not be copied. On success, this will be

zero.

源文件:

app.c:

#include

#include

#include

#include

#include

#include

#define

GET_AVAIL_LEN _IOR('M', 1, int)

//带一个参数,一个int指针,内核把缓冲区中实际可读的字节数,写入到该指针中去

int

main(int argc, char *argv[])

{

int

fd,count;

fd =

open(argv[1], O_RDWR);

if (fd ==

-1)

{

perror("failed

to open\n");

return

-1;

}

char

buf[25]="this is kernel memcpy!!";

count=read(fd,buf,25);

printf("buf

is [%s]\n", buf);

count =

write(fd, buf, 25);

int avail =

0;

ioctl(fd,

GET_AVAIL_LEN, &avail);

printf("avail

= %d\n", avail);

close(fd);

return

0;

}

Makefile:ifeq

($(KERNELRELEASE),)

KERNDIR :=

/home/gec/software/linux-2.6.35.7-gec-v3.0-gt110

#调用shell命令pwd,并把该命令结果赋值给变量PWD

PWD :=$(shell

pwd)

modules:

make -C $(KERNDIR)

M=$(PWD)  modules

#

-C后面加一个目录,表示让make到该目录下去找Makefile执行,

# modules 告诉make要达成的目标,编译模块,

是不是应该告诉我你的那个模块的目录啊,

#

好让我初始化好了后,去找你的模块代码执行啊,

#

呵呵,通过M=模块目录来指定啊......

clean:

rm -rf *.o *~ core .depend

.*.cmd *.ko *.mod.c .tmp_versions

rm -rf modules.order

Module.symvers

else

obj-m +=

char_driver.o

endif

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值