Linux是一种广泛使用的操作系统,尤其在嵌入式设备领域得到了大规模应用。然而,对于开发人员来说,理解Linux设备开发是非常重要的。因此,本文将深入解析Linux设备开发全面指南,帮助读者掌握Linux设备开发的核心概念和技术。
一、Linux设备驱动
在Linux中,设备驱动是一种负责将设备与操作系统进行交互的模块。其作用是将设备的功能转换为Linux内核的API,从而方便应用程序调用。Linux设备驱动通常包括以下几个部分:
1. 初始化函数:用于初始化设备驱动,并将它注册到系统中。它在设备连接到系统时被调用。
2. 中断处理程序:用于处理设备中断,采用中断服务例程的方式执行。
3. 系统调用:用于向驱动程序发送命令。它们可以由用户空间的应用程序调用。对于一些设备操作,如读取和写入数据,通常需要使用系统调用。
4. 设备控制函数:用于控制设备的工作方式,如开启关闭设备。
二、Linux设备驱动程序开发
Linux设备驱动程序开发的过程非常复杂,需要对系统的编程知识和Linux设备系统的工作原理有深入的了解。下面是一些关键步骤:
1. 设计和实现设备驱动程序:此步骤需要使用C语言进行编程。开发人员需要了解内核API和设备的硬件接口。
2. 构建内核模块:内核模块是一种在运行时将代码加载到内存中的程序。它通常由设备驱动程序组成。开发人员需要使用内核源代码进行模块编译。
3. 安装模块:安装模块是将模块加载到内核的过程。开发人员需要使用inod命令将模块加载到内存中。
4. 测试驱动程序:测试驱动程序是验证驱动程序是否正确工作的过程。开发人员需要编写测试程序以模拟设备操作。
三、Linux设备文件系统
在Linux中,每个设备都对应着一个设备文件。设备文件代表设备,允许应用程序像打开一个标准文件一样与设备进行交互。设备文件通常位于/dev文件夹下,它们按照类型和目的不同,可以分为以下几类:
1. 块设备文件:用于存储数据,如硬盘和CD-ROM。
2. 字符设备文件:用于处理字符输入和输出,如键盘和鼠标。
3. 虚拟文件系统:用于提供对其他文件系统的映射,如/proc和/sys。
四、Linux设备驱动程序示例
下面是一个简单的Linux设备驱动程序示例,它显示了如何实现一个字符设备的基本功能:
“`c
#include
#include
#include
#include
#include
//设备号
#define CHAR_DEVICE_MAJOR 240
#define CHAR_DEVICE_MINOR 0
#define DEVICE_NAME “charDevice”
MODULE_LICENSE(“GPL”);
static struct cdev cdev;
static struct class *class;
static int charDevice_open(struct inode * inode, struct file * file)
{
printk(“charDevice_open\n”);
return 0;
}
static long charDevice_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
printk(“charDevice_ioctl\n”);
return 0;
}
static int charDevice_release(struct inode * inode, struct file * file)
{
printk(“charDevice_release\n”);
return 0;
}
static ssize_t charDevice_read(struct file * file, char __user * buf, size_t size, loff_t * offset)
{
printk(“charDevice_read\n”);
return 0;
}
static ssize_t charDevice_write(struct file * file, const char __user * buf, size_t size, loff_t * offset)
{
printk(“charDevice_write\n”);
return 0;
}
static struct file_operations charDevice_fops =
{
.owner = THIS_MODULE,
.open = charDevice_open,
.release = charDevice_release,
.read = charDevice_read,
.write = charDevice_write,
.unlocked_ioctl = charDevice_ioctl,
};
static int charDevice_init(void)
{
dev_t devNo;
int err;
printk(“charDevice_init\n”);
//注册设备号
devNo = MKDEV(CHAR_DEVICE_MAJOR, CHAR_DEVICE_MINOR);
err = register_chrdev_region(devNo, 1, “charDevice”);
//初始化字符设备
cdev_init(&cdev, &charDevice_fops);
cdev.owner = THIS_MODULE;
err = cdev_add(&cdev, devNo, 1);
//创建设备文件
class = class_create(THIS_MODULE, “charDevice”);
device_create(class, NULL, devNo, NULL, “charDevice”);
return err;
}
static void charDevice_exit(void)
{
dev_t devNo;
printk(“charDevice_exit\n”);
//摧毁设备文件
devNo = MKDEV(CHAR_DEVICE_MAJOR, CHAR_DEVICE_MINOR);
device_destroy(class, devNo);
class_destroy(class);
//移除字符设备
cdev_del(&cdev);
//释放设备号
unregister_chrdev_region(devNo, 1);
}
module_init(charDevice_init);
module_exit(charDevice_exit);
“`
以上代码实现了一个字符设备驱动程序,其中包括设备初始化,设备文件系统和设备驱动程序示例。此示例主要用于用于演示和说明Linux设备驱动程序的结构和实现方法。
五、
相关问题拓展阅读:
- 如何系统的学习Linux驱动开发
- linux设备驱动开发详解 第三版什么时候出
如何系统的学习Linux驱动开发
无根之木不活,无基之楼不立,无论是学习哪个领域知识,基础是重中之重。
针对学习linux驱动,滑卜我们来仔细谈谈:
个人认为C语言和数据结构就是重中之重!linux系统更优秀的地方就在于内核。无论悄纯是进程调度,内存管理,还是数据的组织形式,而这些算法的基础就是一个高效而精简的数据结构,红黑树也好,内核链表也好,看不懂C语言,搞不清数据结构,基本上谈不上做驱动。基础不扎实不要谈上层建筑,资源不全面就不要谈合作,很简单的道理。
假设楼主的C语言和数据结构都是勉强过关的(分得清指针函数函数指针,能够熟练自定义链表并进信运穗行增删改查等链表或树的算法重组)。我们来谈下一个阶段,让我们对类Unix系统的基本操作有一个基本的了解,打开文件,切换路经,更改个密码,填个用户什么的。
接下来我们来谈驱动,做驱动就得有做驱动的样子,不知道楼主之前玩过裸板51或者AVR没有,如果没有,尽快搞通,资源很多,跟着人家把电平配一遍,来个矩阵键盘,组个跑马灯,有成就感的同时也打下了硬件基础,好处大大滴。
基于操作系统的驱动,即必须了解内核对驱动程序的管理方法,怎样划分一个具体的设备,是字符型?块设备?还是别的什么,或者从总线的角度来划分一个设备,i2c也好,USB也罢,还是数据地址控制这么一个三总线形式,都是有必要掌握的东西。
上升到应用,任何一个实践项目中很难会有一个板子让你从零开始写驱动,百分之九十九是拿来改,这就是一个熟练的过程,做到一定多的东西,就会下意识地觉得,这个地方可能会有问题,我需要留意一下。
总结一下,驱动工程师其实也是一种硬件翻译型工程师,其任务就是看懂手册上的表格,然后体现在代码里面。客观地说,我们这个行业门槛较高,队友较少,不过等到真正入门之后,那种成就感是无与伦比的。
在学习之前一直对驱动开发非常的陌生,感觉有点神秘。不知道驱动开发和普通的程序开发究竟有什么不同;它的基本框架又是什么样的;他的开发环境有什么特殊的地方;以及怎么写编写一个简单的字符设备驱动前编译加载,下面我就对这些问题一个一个的介绍。
一、驱动的基本框架
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
可以读读内核源码,比如说minix。你看的那本书还不错,还有一本“设备驱动程序”,好像是电力出版社的。另外你可以搞两台虚拟机试着做做东西,写程序经验和感觉都很重要。
个人感觉书还是要纸质的书,确实如此,网上下的东西,看起来总觉得力不从心。
我旁让前个人的感觉是有兴趣内核源码要研究一下,很有帮助。另外有时候写东西对概念的理解很重要,VC其实也不错,因为运清现在会VC的人越来越少了,我曾经做过一段时间,滑羡那是n年前的事情了,因为一直做Linux和JAVA,现在已经彻底丢了,我们是做设备的,LInux,windows都需要做驱动,我们一直招这种人招不到,即使招到也是做应用的。
做嵌入式应用的话一般的编程就可以了。那么嵌入式驱动开陆纯发与内核开发的话就需要学习多个方面的知识。我就把这方面的要求给你交流一下:
(一家之言啊,自己多年从事嵌入式开发的一点感悟)
嵌入式驱动开发需要了解的知识大概有以下几类:
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设备驱动开发详解 第三版什么时候出
这本书已经出版了,因为更换了出版社,所以喊衫不能叫第三版。现在的名字凯陆叫盯渗顷《
linux设备驱动开发详解:基于最新的Linux4.0内核》,这是名副其实的第三版。
《Linux设备驱动开发详解(第3版)》的最新进展。
2023.8.22 目前初步完成2-11章以及第22章 《Linux设备驱动的调试》,相对于第2版,这几章主要的变更。
是修正或升级;是新增知识点;是删除的内容
第2章 《驱动设计的硬件基础》
增加关于SoC的介绍;
增加关于eFuse的内容;
删除ISA总线的内容了;
增加关于SPI总线的介绍;
山嫌慎 增加USB 3.0的介绍;
修正USB同步传输方式英文名;
删除关于cPCI介者拆绍;
增加关于PCI Express介绍;
增加关于Xilinx ZYNQ的介绍;
逗敬 增加SD/SDIO/eMMC的章节;
删除“原理图分析的内容”一节;
增加通过逻辑分析仪看I2C总线的例子;
关于linux设备开发详解的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。