第一个linux驱动

准备:

JZ2440开发板烧写好uboot、linux、文件系统、

启动后开始运行

我们将编译好的驱动复制到nfs文件夹,开发板就挂载这个网络文件系统访问这个驱动程序。

前提:

参考https://blog.csdn.net/RadianceBlau/article/details/55259627

学习第一个linux驱动,建议使用现成的linx驱动源文件!!!

先看猪跑,再吃猪肉!!

Linux内核模块的特点:

1,  模块本身不被编译进内核镜像,能够控制内核的大小。

2,  模块可以在需要的时候中被动态加载,一旦加载完成就和内核其它部分完全一样。

下面便是linux内核模块的helloworld程序,结构十分固定。编译完成后生成hello.ko,通过insmod hello.ko进行加载,加载时输出” hello module has been mount!”,使用rmmod hello进行卸载,卸载时输出” hellomodule has been remove!”。
 

#include <linux/init.h>
#include <linux/module.h>
 
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Radia");
 
static int hello_init()
{
	printk(KERN_EMERG "hello module has been mount!\n");
	return 0;
}
 
static void hello_exit()
{
	printk(KERN_EMERG "hello module has been remove!\n");
}
 
module_init(hello_init);
module_exit(hello_exit);

 

在Linux下可以通过两种方式加载驱动程序:静态加载和动态加载。

  • 静态加载就是把驱动程序直接编译进内核,系统启动后可以直接调用。静态加载的缺点是调试起来比较麻烦,每次修改一个地方都要重新编译和下载内核,效率较低。若采用静态加载的驱动较多,会导致内核容量很大,浪费存储空间。
  • 动态加载利用了Linux的module特性,可以在系统启动后用insmod命令添加模块(.ko),在不需要的时候用rmmod命令卸载模块,采用这种动态加载的方式便于驱动程序的调试,同时可以针对产品的功能需求,进行内核的裁剪,将不需要的驱动去除,大大减小了内核的存储容量。

在台式机上,一般采用动态加载的方式;在嵌入式产品里,可以先采用动态加载的方式进行调试,调试成功后再编译进内核。
 

 

 

 

在服务器编译驱动程序 再复制到网络文件系统给开发板

先分析makefie用户。

  • makefile指定arm内核(要先编译好)的路径 /work/system/linux-2.6.22.6
  • 进入内核目录,在此目录(pwd)执行make modeule命令,modeule意思是把驱动编译成模块
  • obj-m    += first_drv.o意思 有一个模块需要从目标文件first_drv.o中构造,构造的模块名称为first_drv.ko.
  1. 执行makefile就把first_drv.o 目标通过makeile 就生成first_drv.ko.文件         驱动程序编译为.ko文件
  2. 在arm-linux-gcc -o firstdrvtest firstdrvtest.c 生成可执行文件firstdrvtest          main函数编译为可执行文件

把.ko驱动模块复制到网络文件系统,开发板通过nfs挂载网络文件系统

insmod ./firstdrvtest .ko加载驱动模块到内核

./firstdrvtest     执行程序调用驱动程序

book@book-desktop:/work/drivers_and_test/first_drv$ cat Makefile 
KERN_DIR = /work/system/linux-2.6.22.6

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

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order

obj-m	+= first_drv.o

 

 

以下内容在开发板终端操作

内核模块和应用程序
(1)每个内核模块只注册自己以便来服务将来的请求, 并且它的初始化函数立刻终止. 换句话说, 模块初始化函数的任务是为以后调用模块的函数做准备; 好像是模块说, ” 我在这里, 这是我能做的.”模块的退出函数(例子里是 hello_exit)就在模块被卸载时调用. 它好像告诉内核, “我不再在那里了, 不要要求我做任何事了.” 每个内核模块都是这种类似于事件驱动的编程方法, 但不是所有的应用程序都是事件驱动的. 
(2)事件驱动的应用程序和内核代码的退出函数不同: 一个终止的应用程序可以在释放资源方面”懒惰”, 或者完全不做清理工作, 但是模块的退出函数必须小心恢复每个由初始化函数建立的东西, 否则会保留一些东西直到系统重启.
 (3) 一个模块在内核空间运行, 而应用程序在用户空间运行. 内核空间和用户空间特权级别不同,而且每个模式有它自己的内存映射–它自己的地址空间. 
(4) 内核编程与传统应用程序编程方式很大不同的是并发问题. 大部分应用程序, 多线程的应用程序是一个明显的例外, 典型地是顺序运行的, 从头至尾, 不必要担心其他事情会发生而改变它们的环境. 内核代码没有运行在这样的简单世界中, 即便最简单的内核模块必须在这样的概念下编写, “很多事情可能马上发生”. 
 

# cat /proc/devices 
显示出字符设备和块设备两类设备,显示出设备的主设备好和名字。

# insmod first_drv.ko 

insmod命令用于将给定的模块加载到内核中。Linux有许多功能是通过模块的方式,在需要时才载入kernel。如此可使kernel较为精简,进而提高效率,以及保有较大的弹性。这类可载入的模块,通常是设备驱动程序。例如:insmod xxx.ko 

rmmod命令用于从当前运行的内核中移除指定的内核模块。执行rmmod指令,可删除不需要的模块。Linux操作系统的核心具有模块化的特性,应此在编译核心时,务须把全部的功能都放入核心。你可以将这些功能编译成一个个单独的模块,待有需要时再分别载入它们。例如:rmmod xxx.ko
# cat /proc/devices 
Character devices:252 first_drv 


驱动设备号

  • ·系统分配(推荐)
  • ·手动分配

1.1系统设备号自动分配,驱动怎么写

注册驱动的时候写0

int major;
static int first_drv_init(void)
{
    major = register_chrdev(0, "first_drv", &first_drv_fops); // 注册, 告诉内核

//这里三个参数,0表示系统自动分配主设备号,第二个是任意名字,第三个是申请的结构体实体数据类型是static struct //file_operations

}

static void first_drv_exit(void)
{
    unregister_chrdev(major, "first_drv"); // 卸载
}

1.2系统设备号手动分配,驱动怎么写

查看设备号的空缺项,写入这个驱动

    major = register_chrdev(空缺的主设备号, "first_drv", &first_drv_fops); // 注册, 告诉内核

应用程序打开一个设备文件原理

 

fd = open("/dev/xyz", O_RDWR);这个/dev/xyz是怎么来的?

  • 手工建立      mknod  /dev/xyz c 主设备号 次设备号
  • 自动创建  dev机制   根据系统信息创建设备节点(推荐)

如何自动创建设备节点信息?

驱动个里面定义两个变量
static struct class *firstdrv_class;
static struct class_device    *firstdrv_class_dev;

······注册驱动后,建立一个类, 类下创建一个设备/dev/xyz

`````注意这类这相关要声明license才能用 ,写    MODULE_LICENSE("GPL");

这样就在/dev下自动生成xyz这个设备,

           在/sys目录下class自动生成相关信息

# ls /dev/xyz    
/dev/xyz
# ls /dev/xyz
/dev/xyz


# cd /sys/class/ firstdrv/ xyz
# ls
dev        subsystem  uevent

# cat dev
252:0

static struct class *firstdrv_class;
static struct class_device    *firstdrv_class_dev;

int major;
static int first_drv_init(void)
{
	major = register_chrdev(0, "first_drv", &first_drv_fops); // 注册, 告诉内核

	firstdrv_class = class_create(THIS_MODULE, "firstdrv");

	firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/xyz */

//ioremap函数 将物理地址映射为虚拟地址
	gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
	gpfdat = gpfcon + 1;

	return 0;
}
static void first_drv_exit(void)
{
	unregister_chrdev(major, "first_drv"); // 卸载

	class_device_unregister(firstdrv_class_dev);
	class_destroy(firstdrv_class);
	iounmap(gpfcon);
}

MODULE_LICENSE("GPL");
查看dev下自动生成的设备xyz
# ls /dev/xyz
/dev/xyz

卸载驱动
# rmmod first_drv

卸载后自动清除
# ls /dev/xyz
ls: /dev/xyz: No such file or directory
 


 再加载驱动
# cd /mnt/
# insmod ./first_drv.ko 

加载驱动后有自动生成
# ls /dev/xyz
/dev/xyz

 


查看和卸载驱动

# lsmod 
# rmmod  first_drv              

cat  /proc/devices查看注册进内核的驱动程序。

在当前文件目录下进行make modules,生成驱动模块led_drv.ko,将他放到单板根文件系统的/lib/modules/2.622.6/目录下,然后“insmod led_drv.ko”命令进行加装进内核,“rmmod led_drv.ko”为卸载命令。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值