前言:现在所记录的是以前工作学习所留下来的一些断断续续的笔记,现在将它们整理出来,希望能带给到正在学习这方面知道的同学一些帮助。如若发现有误之处,也请不吝指出,不胜感激。
我们常常听人说Linux操作系统下,一切皆是文件。linux系统主要有3类的设备文件类型:块设备、字符设备和网络设备。那么驱动也主要包括三类设备的驱动,分别是字符设备驱动、块设备驱动以及网络设备驱动。
1、字符设备:是指能够像字节流串行顺序依次进行访问的设备,对它的读写是以字节为单位。字符设备的上层没有磁盘文件系统,所以字符设备的file_operations成员函数就直接由字符设备驱动提供(一般字符设备都会实现相应的fops集),因此file_operations 也就成为了字符设备驱动的核心。
类型举例:鼠标,键盘,串口,LED、GPIO等等.
类型举例:鼠标,键盘,串口,LED、GPIO等等.
2、块设备:以数据块的形式存放数据,如NAND Flash以页为单位存储数据,并采用mount方式挂载块设备。
块设备必须能够随机存取(random access),字符设备则没有这个要求。块设备除了给内核提供和字符设备一样的接口外,还提供了专门面向块设备的接口,块设备的接口必须支持挂装文件系统,通过此接口,块设备能够容纳文件系统,因此应用程序一般通过文件系统来访问块设备上的内容,而不是直接和设备打交道。
对于块设备而言,上层ext2,jiffs2,fat等文件系统会 实现针对VFS的file_opertations成员函数,所以设备驱动层将看不到file_opeations的存在。磁盘文件系统和设备驱动会将对磁盘上文件的访问转换成对磁盘上柱面和扇区的访问。
类型举例:硬盘,磁盘,U盘,SD卡等等。
块设备必须能够随机存取(random access),字符设备则没有这个要求。块设备除了给内核提供和字符设备一样的接口外,还提供了专门面向块设备的接口,块设备的接口必须支持挂装文件系统,通过此接口,块设备能够容纳文件系统,因此应用程序一般通过文件系统来访问块设备上的内容,而不是直接和设备打交道。
对于块设备而言,上层ext2,jiffs2,fat等文件系统会 实现针对VFS的file_opertations成员函数,所以设备驱动层将看不到file_opeations的存在。磁盘文件系统和设备驱动会将对磁盘上文件的访问转换成对磁盘上柱面和扇区的访问。
类型举例:硬盘,磁盘,U盘,SD卡等等。
3、网络设备在Linux内核中是唯一不体现一切皆设备思想的驱动架构,因为网络设备使用套接字来实现网数据的接受和发送。
网络设备驱动不同于字符设备和块设备,不在/dev下以文件节点代表,而是通过单独的网络接口来代表。
类型举例:网卡
网络设备驱动不同于字符设备和块设备,不在/dev下以文件节点代表,而是通过单独的网络接口来代表。
类型举例:网卡
那么以下我们着重学习的是——
字符设备驱动
。
一、字符设备驱动之——ko模式驱动开发
1,基本的四要素:
a,头文件声明
#include <linux/init.h>
#include <linux/module.h>
b,模块加载函数
如:static int __init hello_init(void)
c,模块卸载函数
如:static void __exit hello_exit(void)
d,开源声明
MODULE_LICENSE("GPL");
当然了,还须要这样声明一下:
//加载模块函数
module_init(hello_init);
//卸载模块函数
module_exit(hello_exit);
2,一些小技巧
//驱动模块作者声明
MODULE_AUTHOR("CHEN JUN");
//驱动版本声明
MODULE_VERSION("V1.0.0");
//本驱动的相关描述
MODULE_DESCRIPTION("This is a driver 'hello' example ");
//传入变量的声明
module_param(age,int,0644);module_param(str,charp,0644);
说明:以上均为在自己要编写的驱动文件.c 里进行。
依据国际惯例,以一声hello world而得以诞生。以下是我们编写的第一个简单驱动文件hello.c的具体内容:
/*1. 模块的头文件*/
#include <linux/init.h>
#include <linux/module.h>
static int a=1;
static char *str = "hello world";
/*2. 模块加载函数*/
static int __init hello_init(void)
{
printk(KERN_INFO"%s():%d\n", __func__,__LINE__);
printk(KERN_INFO"%s(): a=%d str=%s\n", __func__, a, str);
return 0;
}
/*3. 模块卸载函数*/
static void __exit hello_exit(void)
{
printk(KERN_INFO"%s():%d\n", __func__,__LINE__);
}
/*
** param1: 变量
** param2: 变量类型
** param3: 权限
*/
module_param(a,int,S_IRWXU);
module_param(str,charp,S_IRWXU);
/*4. 模块许可声明*/
module_init(hello_init);
module_exit(hello_exit);
MODULE_AUTHOR("chenjun");
MODULE_VERSION("v1.0");
MODULE_DESCRIPTION("This is a simple driver module!");
MODULE_LICENSE("GPL");
3,Makefile的编写
//首先一进来就是先判断KERNELRELEASE 是否为空? KERNELRELEASE 在内核源码里应该有定义,
//第一个进来编译时KERNELRELEASE 一定是为空的。
ifeq ($(KERNELRELEASE),)
//同样,KERNELDIR 也是定义为当前内核源码所在的路径
KERNELDIR = /home/chen/xxx/drive/linux-3.0.8
//PWD 为当前要编译的代码的路径
PWD =$(shell pwd)
//modules 是一个伪规则
//值得一说的是:make -C 的含义,是编译指定目录下的某个文件。M表示为指定目录下的模块
以下是
Makefile的具体内容:
ifeq ($(KERNELRELEASE),)//变量
KERNELDIR =/home/chen/work/kernel/linux-3.0.8 ###将编译好的内核路径赋值给KERNELDIR
PWD =$(shell pwd)
modules: ###进入内核路径,获取其中的Makefile,利用该Makefile编译所有的*.c--->*.o文件,同时将KERNELRELEASE赋值
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
cp hello.ko /opt/filesystem/s5pc100
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
clean:
rm -rf *.so *.o *.ko .tmp_versions *.mod.c *.order *.symvers
else
obj-m :=hello.o ###这个比较重要,意思是说把当前的那个.c文件编译成 ko文件
endif
说明:实际上,在做一次make 去编译一个驱动文件,会进入2次Makefile。
4,在目标板上在运行ko文件
a, insmod xxx.ko ,当然了,如果这个驱动是要手动去创建设备节点的话,在这之前还得手动去创建设备节点。
例如:
手动创建设备节点:
mknod /dev/hello c 250 0
注释:mknod 为创建设备节点命令;
/dev/hello 是说创建一个名为hello的设备节点在/dev目录下;
c 即表示是字符设备
250 表示主设备号
0 表示次设备号
但是今天我们这个是简单的例子,只在加载和卸载驱动文件时打印一句话,就没必要去创建设备节点,以下权当是明天的储备知识点来看。
二、设备节点
1,作用:为应用提供访问接口。
为什么:一般地,系统会将所有的字符驱动当作对像来看待,既然是对像,那么就有 属性和方法。
所以说,我们一般会是以面向对像的方法去编写驱动文件。
但是,又说:几乎所有的对像皆文件。所以说,其实就是对文件的操作。
2,驱动要和应用空间打交道
a,驱动需要申请号码。
b,要有一个文件存在,将应用和驱动关联起来。
c,应用如果去操作驱动(驱动要提供操作方法)。
一、驱动需要申请号码。
静态申请设备号并注册驱动到系统中:
register_chrdev(主设备号,"驱动名称",&dev_fops(操作方法接口));
二、要有一个文件存在,将应用和驱动关联起来
第一天,我们就用手动创建设备节点(文件)的方法来做。
三、就是在应用上对驱动操作了。