Linux驱动程序:文件读写操作 (linux 驱动读写文件)

Linux是一个开放源代码的操作系统,它给开发者提供了许多强大的工具和框架,方便他们开发驱动程序、系统软件、网络应用等各…

Linux是一个开放源代码的操作系统,它给开发者提供了许多强大的工具和框架,方便他们开发驱动程序、系统软件、网络应用等各种应用程序。其中,驱动程序是Linux系统的一个关键部分,因为它控制硬件设备的输入和输出,与内核交互,负责从设备中读取数据和向设备中写入数据。

在Linux中,文件I/O是一种非常常见和实用的操作,因为它使应用程序可以通过文件系统读取和写入数据。而Linux驱动程序就可以通过文件I/O API和文件系统交互,实现对设备的读写操作。这篇文章将主要介绍文件读写操作在Linux驱动程序中的应用。

1.文件I/O API简介

对于Linux驱动程序,文件I/O API是一个非常重要的接口,它提供了诸如open、close、read、write、ioctl等函数,这些函数是文件句柄的一部分,并且可以与用户进程进行交互。下面简单介绍一下这些API函数的作用:

1.1. open函数

open函数是用于打开文件的函数,其定义为int open(const char *path, int flags, mode_t mode)。在Linux中,每一个进程都有一个类似于文件句柄的数据结构,叫做文件描述符。open函数在打开文件时会返回一个文件描述符,这个文件描述符是用户进程与内核之间联系的唯一标识符。在驱动程序中,开发者可以通过open函数打开设备,并且跟踪该设备的状态。

1.2. close函数

close函数是用于关闭文件、释放文件描述符的函数,其定义为int close(int fd)。close函数在完成驱动程序对设备的操作之后,用于关闭设备,释放相关资源。这个函数的调用会引起驱动程序中的close函数处理,从而完成对设备的关闭操作。

1.3. read函数

read函数是用于从设备中读取数据的函数,其定义为ssize_t read(int fd, void *buf, size_t count)。在驱动程序中,开发者可以通过read函数读取从设备传输的数据,从而实现数据的接收。在调用read函数时,内核会将各个请求进行合并,以减少内核调用的次数,从而提高I/O性能。

1.4. write函数

write函数是用于向设备中写入数据的函数,其定义为ssize_t write(int fd, const void *buf, size_t count)。在驱动程序中,开发者可以通过write函数将数据写入设备中,以实现对设备的控制。当调用write函数时,内核会将数据缓存,并且在idle时段将数据写入设备。

1.5. ioctl函数

ioctl函数是用于进行设备控制的函数,其定义为int ioctl(int fd, unsigned int cmd, unsigned long arg)。在开发驱动程序时,开发者可以使用ioctl函数直接进行设备控制,这样能够方便地实现自定义操作。ioctl函数不是标准的POSIX函数,因此它具有不同的命名空间,不同的ioctl命令需要采用不同的编号空间。

2.示例:LED驱动程序

现在,我们来看一个具体的例子:LED驱动程序。这个驱动程序中,我们可以通过文件I/O操作控制LED的开关,让它一直处于开启或关闭状态。

2.1. 设备注册和初始化

在Led驱动程序中,我们需要对设备进行注册并且进行设备初始化。在驱动程序中,设备注册是通过设备模型实现的,也就是说,我们需要注册设备模型,然后再注册设备。在驱动程序中,设备结构体包含了设备相关的所有属性,这样就可以让驱动程序与应用程序进行交互。

2.2. 设备打开和关闭操作

设备打开和关闭操作是针对应用程序,提供了与驱动程序交互的接口。在打开设备时,可以使用open函数,同样,在关闭设备时,可以使用close函数,在这些函数中,开发者可以实现一些特定的操作,例如在打开设备时,可以执行一些初始化操作,而关闭设备时,可以进行一些善后工作。

2.3. 数据读取和写入操作

数据读取和写入操作是我们在驱动程序中经常使用的操作,通过这些操作,我们可以实现数据的传递。在读取设备数据时,可以使用read函数,而在向设备中写入数据时,可以使用write函数,这样就能实现设备数据传输的功能。同时,在实现LED驱动程序的过程中,开发者还可以使用ioctl函数控制设备状态。

3.结论

在Linux驱动程序中,文件读写操作是一个非常常用,实用的操作。它可以实现数据的传递和控制设备状态。在Linux中,有许多基础API可以执行文件读写操作,例如open、close、read、write和ioctl函数。通过这些函数,驱动程序可以更加方便地控制设备,并且实现数据传输。在开发驱动程序时,我们可以利用这些API,从而更好地实现各种功能。特别是在一些Linux内核模块和驱动程序中,文件读写操作是不可或缺的,因此需要对其进行深入了解。

相关问题拓展阅读:

  • 如何系统的学习Linux驱动开发

如何系统的学习Linux驱动开发

可以读读内核源码,比如说minix。你看的那本书还不错,还有一本“设备驱动程序”,好像是电力出版社的。另外你可以搞两台虚拟机试着做做东西,写程序经验和感觉都很重要。

个人感觉书还是要纸质的书,确实如此,网上下的东西,看起来总觉得力不从心。

我个人的感觉是有兴趣内核源码要研究一下,很有帮助。另外有时候写东西对概念的理解很重要,VC其实也不错,因为现在会VC的人越来越少了,我曾经做过一段时间,那是n年前的事情了,因为一直做Linux和JAVA,现在已经彻底丢了,我们是做设备的,LInux,windows都需要做驱动,我们一直招这种人招不到,即使招到也是做应用的。

在学习之前一直对驱动开发非常的陌生,感觉有点神秘。不知道驱动开发和普通的程序开发究竟有什么不同;它的基本框架又是什么样的;他的开发环境有什么特殊的地方;以及怎么写编写一个简单的字符设备驱动前编译加载,下面我就对这些问题一个一个的介绍。

一、驱动的基本框架

1.  那么究竟什么是驱动程序,它有什么用呢:

l     驱动是硬件设备与应用程序之间的一个中间软件层

l 它使得某个特定硬件能够响应一个定义良好的内部编程接口,同时完全隐蔽了设备的工作细节

l     用户通过一组与具体设备无关的标准化的调用来完成相应的操作

l 驱动程序的任务就是把这些标准化的系统调用映射到具体设备对于实际硬件的特定操作上

l     驱动程序是内核的一部分,可以使用中断、DMA等操作

l     驱动程序在用户态和内核态之间传递数据

2.  Linux驱动的基本框架

3.  Linux下设备驱动程序的一般可以分为以下三类

1)字符设备

a)所有能够象字节流一样访问的设备都通过字符设备来实现

b)它们被映射为文件系统中的节点,通常在/dev/目录下面

c)一般要包含open read write close等系统调用的实现

2)块设备

d)通常是指诸如磁盘、内存、Flash等可以容纳文件系统的存储设备。

e)块设备也是通过文件系统来访问,与字符设备的区别是:内核管理数据的方式不同

f)它允许象字符设备一样以字节流的方式来访问,也可一次传递任意多的字节。

3)网络接口设备

g)通常它指的是硬件设备,但有时也可能是一个软件设备(如回环接口loopback),它们由内核中网络子系统驱动,负责发送和接收数据包。

h)它们的数据传送往往不是面向流的,因此很难将它们映射到一个文件系统的节点上。

 

二、怎么搭建一个驱动的开发环境

因为驱动是要编译进内核,在启动内核时就会驱动此硬件设备;或者编译生成一个.o文件, 当应用程序需要时再动态加载进内核空间运行。因此编译任何一个驱动程序都要链接到内核的源码树。所以搭建环境的之一步当然是建内核源码树

1.怎么建内核源码树

a) 首先看你的系统有没有源码树,在你的/lib/ modules目录下会有内核信息,比如我当前的系统里有两个版本:

#ls /lib/ modules

2.6.15-rc7  2.6.21-1.3194.fc7

查看其源码位置:

## ll /lib/modules/2.6.15-rc7/build

lrwxrwxrwx 1 root root 19:19 /lib/modules/2.6.15-rc7/build -> /root/xkli/linux-2.6.15-rc7

发现build是一个链接文件,其所对应的目录就是源码树的目录。但现在这里目标目录已经是无效的了。所以得自己重新下载

b)下载并编译源码树

有很多网站上可以下载,但官方网址是:

下载完后当然就是解压编译了

# tar –xzvf linux-2.6.16.54.tar.gz

#cd linux-2.6.16.54

## make menuconfig (配置内核各选项,如果没有配置就无法下一步编译,这里可以不要改任何东西)

#make

如果编译没有出错。那么恭喜你。你的开发环境已经搭建好了

三、了解驱动的基本知识

1.设备号

1)什么是设备号呢?我们进系统根据现有的设备来讲解就清楚了:

#ls -l /dev/

crwxrwxrwx 1 root root     1,1 16:36 null

crwroot root     4,1 16:35 systty

crw-rw-rw- 1 root tty,1 16:36 tty

crw-rwroot tty,1 16:35 tty0

在日期前面的两个数(如之一列就是1,3)就是表示的设备号,之一个是主设备号,第二个是从设备号

2)设备号有什么用呢?

l 传统上, 主编号标识设备相连的驱动. 例如, /dev/null 和 /dev/zero 都由驱动 1 来管理, 而虚拟控制台和串口终端都由驱动 4 管理

l 次编号被内核用来决定引用哪个设备. 依据你的驱动是如何编写的自己区别

3)设备号结构类型以及申请方式

l   在内核中, dev_t 类型(在 中定义)用来持有设备编号, 对于 2.6.0 内核, dev_t 是 32 位的量, 12 位用作主编号, 20 位用作次编号.

l   能获得一个 dev_t 的主或者次编号方式:

  MAJOR(dev_t dev); //主要

MINOR(dev_t dev);//次要

l   但是如果你有主次编号, 需要将其转换为一个 dev_t, 使用: MKDEV(int major, int minor);

4)怎么在程序中分配和释放设备号

在建立一个字符驱动时需要做的之一件事是获取一个或多个设备编号来使用. 可以达到此功能的函数有两个:

l一个是你自己事先知道设备号的

register_chrdev_region, 在 中声明:

int register_chrdev_region(dev_t first, unsigned int count, char *name);

first 是你要分配的起始设备编号. first 的次编号部分常常是 0,count 是你请求的连续设备编号的总数. name 是应当连接到这个编号范围的设备的名子; 它会出现在 /proc/devices 和 sysfs 中.

l第二个是动态动态分配设备编号

int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);

使用这个函数, dev 是一个只输出的参数, 它在函数成功完成时持有你的分配范围的之一个数. fisetminor 应当是请求的之一个要用的次编号; 它常常是 0. count 和 name 参数如同给 request_chrdev_region 的一样.

5)设备编号的释放使用

不管你是采用哪些方式分配的设备号。使用之后肯定是要释放的,其方式如下:

void unregister_chrdev_region(dev_t first, unsigned int count);

6)

2.驱动程序的二个最重要数据结构

1)file_operation

倒如字符设备scull的一般定义如下:

struct file_operations scull_fops = {

.owner =  THIS_MODULE, 

 .llseek =  scull_llseek, 

 .read =  scull_read, 

 .write =  scull_write, 

 .ioctl =  scull_ioctl, 

 .open =  scull_open, 

 .release =  scull_release,  

};

file_operation也称为设备驱动程序接口

定义在 , 是一个函数指针的. 每个打开文件(内部用一个 file 结构来代表)与它自身的函数相关连( 通过包含一个称为 f_op 的成员, 它指向一个 file_operations 结构). 这些操作大部分负责实现系统调用, 因此, 命名为 open, read, 等等

2)File

定义位于include/fs.h

struct file结构与驱动相关的成员

lmode_t f_mode      标识文件的读写权限

lloff_t f_pos当前读写位置

lunsigned int_f_flag 文件标志,主要进行阻塞/非阻塞型操作时检查

lstruct file_operation * f_op  文件操作的结构指针

lvoid * private_data 驱动程序一般将它指向已经分配的数据

lstruct dentry* f_dentry  文件对应的目录项结构

3.字符设备注册

1)内核在内部使用类型 struct cdev 的结构来代表字符设备. 在内核调用你的设备操作前, 必须编写分配并注册一个或几个这些结构. 有 2 种方法来分配和初始化一个这些结构.

l如果你想在运行时获得一个独立的 cdev 结构,可以这样使用:

struct cdev *my_cdev = cdev_alloc();

my_cdev->ops = &my_fops;

l如果想将 cdev 结构嵌入一个你自己的设备特定的结构; 你应当初始化你已经分配的结构, 使用:

void cdev_init(struct cdev *cdev, struct file_operations *fops);

2)一旦 cdev 结构建立, 最后的步骤是把它告诉内核, 调用:

int cdev_add(struct cdev *dev, dev_t num, unsigned int count);

说明:dev 是 cdev 结构, num 是这个设备响应的之一个设备号, count 是应当关联到设备的设备号的数目. 常常 count 是 1, 但是有多个设备号对应于一个特定的设备的情形.

3)为从系统去除一个字符设备, 调用:

void cdev_del(struct cdev *dev);

4.open 和 release

无根之木不活,无基之楼不立,无论是学习哪个领域知识,基础是重中之重。

针对学习linux驱动,我们来仔细谈谈:

个人认为C语言和数据结构就是重中之重!Linux系统更优秀的地方就在于内核。无论是进程调度,内存管理,还是数据的组织形式,而这些算法的基础就是一个高效而精简的数据结构,红黑树也好,内核链表也好,看不懂C语言,搞不清数据结构,基本上谈不上做驱动。基础不扎实不要谈上层建筑,资源不全面就不要谈合作,很简单的道理。

假设楼主的C语言和数据结构都是勉强过关的(分得清指针函数函数指针,能够熟练自定义链表并进行增删改查等链表或树的算法重组)。我们来谈下一个阶段,让我们对类Unix系统的基本操作有一个基本的了解,打开文件,切换路经,更改个密码,填个用户什么的。

接下来我们来谈驱动,做驱动就得有做驱动的样子,不知道楼主之前玩过裸板51或者AVR没有,如果没有,尽快搞通,资源很多,跟着人家把电平配一遍,来个矩阵键盘,组个跑马灯,有成就感的同时也打下了硬件基础,好处大大滴。

基于操作系统的驱动,即必须了解内核对驱动程序的管理方法,怎样划分一个具体的设备,是字符型?块设备?还是别的什么,或者从总线的角度来划分一个设备,i2c也好,USB也罢,还是数据地址控制这么一个三总线形式,都是有必要掌握的东西。

上升到应用,任何一个实践项目中很难会有一个板子让你从零开始写驱动,百分之九十九是拿来改,这就是一个熟练的过程,做到一定多的东西,就会下意识地觉得,这个地方可能会有问题,我需要留意一下。

总结一下,驱动工程师其实也是一种硬件翻译型工程师,其任务就是看懂手册上的表格,然后体现在代码里面。客观地说,我们这个行业门槛较高,队友较少,不过等到真正入门之后,那种成就感是无与伦比的。

做嵌入式应用的话一般的编程就可以了。那么嵌入式驱动开发与内核开发的话就需要学习多个方面的知识。我就把这方面的要求给你交流一下:

(一家之言啊,自己多年从事嵌入式开发的一点感悟)

嵌入式驱动开发需要了解的知识大概有以下几类:

1 嵌入式操作系统驱动框架。每一个操作系统都有自己的构架,应该了解驱动在整个系统中的具置与构建驱动程序的主要事项

2 总线知识,比如PCI、USB总线。

3 芯片知识。驱动其实就是对设备上一些寄存器的配置、CPU与设备本身的通讯以及对不同命令的处理

4 要做好驱动,必须对所使用的CPU体系结构有一个比较深刻的认识

5 C++基本用不上,主要是C和汇编。

6 做驱动更好要懂内核调试(比如说linux)

先熟悉module的使用和编写

一、什么是module

从名字上看就是模块的意思,我个人的理解就是一个一个的小程序,可以进行动态的安装和卸载,而在这里面就实现一些功能,其中驱动就是在module中。

二、编写一个最简单的module

编写一个最简单的module只需要两个文件即可:Makefile和xxx.c

(1)Makefile

1 #ubuntu的内核源码树,如果要编译在ubuntu中安装的模块就打开这2个 2 #KERN_VER = $(shell uname -r) 3 #KERN_DIR = /lib/modules/$(KERN_VER)/build

6 #开发板的linux内核的源码树目录 7 KERN_DIR = /root/3288_5.1/kernel

8 9 obj-m += module_test.o10 11 all:make -C $(KERN_DIR) M=`pwd` modules

13 14 .PHONY: clean

15 clean:make -C $(KERN_DIR) M=`pwd` modules clean

在上面的Makefile中,最核心的一句就是make -C $(KERN_DIR) M=`pwd` modules

这句话的意思就是进入到kernel的源码目录中,使用内核源码中的编译规格来进行编译这个模块。

其目的主要是为了将module和kernel保持一致,在加载module时就不会出错。

(2)module_test.c

1 #include// module_init module_exit 2 #include// __init __exit 3 4 // 模块安装函数 5 static int __init chrdev_init(void) 6 {

7 printk(KERN_INFO “chrdev_init helloworld init\n”);return 0;10 }11 12 // 模块卸载函数13 static void __exit chrdev_exit(void)14 {printk(KERN_INFO “chrdev_exit helloworld exit\n”);16 }17 18 module_init(chrdev_init);19 module_exit(chrdev_exit);20 21 // MODULE_xxx这种宏作用是用来添加模块描述信息22 MODULE_LICENSE(“GPL”); // 描述模块的许可证23 MODULE_AUTHOR(“xuan”); // 描述模块的作者24 MODULE_DESCRIPTION(“module test”); // 描述模块的介绍信息25 MODULE_ALIAS(“alias xxx”);// 描述模块的别名信息

(3)编译和安装module

进入到代码的文件夹中,输入:make

然后,将生成的.ko文件放到平台中,输入:inod xxx.ko

根据这个思路去学习

建议在看看这本书《Linux就该这么学》

关于linux 驱动读写文件的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。

香港服务器首选港服(Server.HK),2H2G首月10元开通。
港服(Server.HK)(www.IDC.Net)提供简单好用,价格厚道的香港/美国云服务器和独立服务器。IDC+ISP+ICP资质。ARIN和APNIC会员。成熟技术团队15年行业经验。

为您推荐

ssh远程超时中断的解决办法

有时我们网络正常的,但SSH连接经常出现中断的情况,以及在SSH远程时很慢的问题。 这是由于OpenSSL服务默认启用了...

Linux系统防火墙放行端口

如果您服务器内安装了宝塔面板,请直接登陆宝塔面板,安全,里面添加放行端口。如果添加后不生效,把防火墙开关一下即可。本教程...

Linux主机简单判断被CC攻击的网站命令-比较直接有效

CC攻击很容易发起,并且几乎不需要成本,导致现在的CC攻击越来越多。 大部分搞CC攻击的人,都是用在网上下载的工具,这些...

linux环境下测试get和post请求

Linux环境下测试get和post请求 ?get,post,curl   get请求 curl: curl ...

umount卸载磁盘提示target is busy

umount卸载磁盘提示target is busy. (目标忙) 的问题解决方案   umount卸载磁盘提...
返回顶部