linux驱动编程初级+makefile.docx

上传人:b****6 文档编号:8060389 上传时间:2023-01-28 格式:DOCX 页数:28 大小:537.23KB
下载 相关 举报
linux驱动编程初级+makefile.docx_第1页
第1页 / 共28页
linux驱动编程初级+makefile.docx_第2页
第2页 / 共28页
linux驱动编程初级+makefile.docx_第3页
第3页 / 共28页
linux驱动编程初级+makefile.docx_第4页
第4页 / 共28页
linux驱动编程初级+makefile.docx_第5页
第5页 / 共28页
点击查看更多>>
下载资源
资源描述

linux驱动编程初级+makefile.docx

《linux驱动编程初级+makefile.docx》由会员分享,可在线阅读,更多相关《linux驱动编程初级+makefile.docx(28页珍藏版)》请在冰豆网上搜索。

linux驱动编程初级+makefile.docx

linux驱动编程初级+makefile

驱动编程

1模块的概述2

2sourceinsight加载内核源码方法2

3模块makefile的编写3

4模块makefile编写方法4

5在X86上运行模块:

5

6编写模块5

7模块的加载进内核命令5

8最简单的上层调用+调用驱动方法6

9复杂框架上层应用+驱动调用方法7

10复杂框架字符设备创建并注册过程7

11file_operations常用函数9

12同步互斥操作10

13同步互斥函数总结10

14阻塞IO编程流程11

15轮询操作上层select下层poll12

16信号处理12

17中断13

18中断新模型--上半部中断和下半部中断的实现14

19内核定时器编程15

20内核延时函数15

21内核源代码中头文件分配方式15

22linux内核管理和内核的内存管理16

23设备io端口和io内存访问–如何控制led的亮灭16

24*驱动-设备分离思想编程————内核进阶18

25驱动-设备分离-核心最小架构18

26驱动设备分离思想-上层架构(基于封装)20

27头文件总结23

28设置系统自启动命令u-boot24

第一天需要理清的东西

1)模块的概念,模块与应用的区别

2)模块主要的组成

头文件、module_init()modoule_exit()module_lisence()

3)模块的如何编辑,如何编译,如何加载到内核中运行使用makefile

4)模块驱动编写,必须通过上层应用程序调用。

1模块的概述

模块是内核的一部分,为了防止内核太大,把它放在文件系统里面。

也可以在编译内核的直接编译进内核。

1,存储位置可以在开始时编译进内核,也可以编译进模块,最后加载

2、运行时环境在哪个内核树下编译,就对应这个运行环境

3、模块的编译问题:

前提条件是需要对应的内核源码树,或者必须有对应的内核版本匹配

4、模块编译使用makefile注意makefile的编写

2sourceinsight加载内核源码方法

在windows下创建工程,使用sourceinsight查看内核代码:

2.1先将内核源码拷到对应的文件夹

2.2在sourceinsight里添加工程,筛选需要添加的文件

注意选择

按照树来添加,然后

按照remove来踢出不需要的文件夹

主要添加arm下s5pc100和s5p平台的drivesinclude文件夹等,后面的许多不需要添加。

2.3最后同步

3模块makefile的编写

模块的编译:

1)、模块编译的核心语句:

$(MAKE)-C$(KERNELDIR)M=$(PWD)modules

-C:

进入内核源码树

M=:

返回到当前目录,再次执行该目录下的makefile

eg:

/tnt/abc.c----->abc.ko

1、在/tnt目录下敲make,只有/tnt目录下的Makefile被调用

2、目的是要进入到内核源码树中,一进一回,-C来进,M=带着内核源码树中的makefile的功能回来了

-------

内核源码树中Makefile的目标:

obj-y:

汇集了所有编译进内核的目标文件

obj-m:

汇集了所有编译成模块的目标文件

3、回来过后,我们只有确定obj-m变量的集合

4、makemodules告诉内核的makefile,只做编译模块的功能

4模块makefile编写方法

ifeq($(KERNELRELEASE),)

KERNELDIR:

=/work/linux-2.6.35-farsight

PWD:

=$(shellpwd)

modules:

$(MAKE)-C$(KERNELDIR)M=$(PWD)modules

install:

$(MAKE)-C$(KERNELDIR)M=$(PWD)modules_install

clean:

rm-rf.tmp_versions*.ko*.o.*.cmd*.mod.c*.order*.symvers

.PHONY:

modulesclean

else

obj-m:

=ex1.o

endif

以上是makefile的内容,

●注意原来的内核目录树不要进行makeclean或者makedistclean

●KERNELDIR表示模块加载在哪个内核的文件夹(又叫内核源码树),

●$(MAKE)-C$(KERNELDIR)M=$(PWD)modules表示进入该内核文件夹,将顶层makefile中的内容带回,再重新执行一次该makefile将obj-m:

=ex1.o编译,并执行makemodules(并只编译ex1.c,不编译其它模块)

●$(MAKE)-C$(KERNELDIR)M=$(PWD)modules_install表示执行顶层makefile的modulesinstall标签下的命令

●安装的位置原来默认在/lib下面,所以需要修改其到我们制作的根文件系统下/work/rootfs/

在顶层Makefile位置搜索:

MODLIB

修改为:

●obj-m:

=ex1.o你需要编译的.c的文件名

****************************此时简单的编译环境已经搭建完毕*******************

****************************执行make*******************

****************************执行makeinstall*******************

在/work/rootfs/lib/modules/2.6.35/extra即可找到该模块.ko

*****************************************************************************

搭建好环境,保证虚拟机与板子与计算机网络连通,并设置板子u-boot从nfs挂载,启动内核,并成功通过nfs加载rootfs,此时环境完毕,进入/work/rootfs/lib/modules/2.6.35/extra,找到模块,加载卸装模块操纵

5在X86上运行模块:

修改Makefile中的内核源码树的目录

X86下的内核源码树:

/lib/modules/2.6.35-22-generic/build

如果没有在控制台上交互,默认是看不到信息的,需要dmesg这个命令去查看

6编写模块

模块最小组成如下:

●注意:

module_initmodule_exit必须放在末尾

●注意:

函数的原型返回值

●头文件

7模块的加载进内核命令

insmod

rmmod

lsmod

8最简单的上层调用+调用驱动方法

8.1首先在module_init(abc)abc函数中注册设备

register_chrdev(注册设备号,上层可见的设备名,操作封装)

该函数完成设备注册,在板子上用cat/proc/devices便可以看见该设备

8.2完成fops操作的封装

●注意格式

●必须在函数后面声明该结构体

●头文件#include

8.3查看到该字符设备后,创建设备节点,则上层通过设备字符名与该设备号绑定

mknod/dev/hf_charc2450

ls/dev/可以查看注册的所有设备节点

8.4此时上层应用的open(”hf_char”,O_RDWR),即可完成该设备的打开,即可以完成上层应用于下层驱动相关fops的操作。

由此可以理解:

上层应用通过设备名称在目录/dev/下查找设备,

下层驱动通过设备号如254,在内核注册,在/porc/devices查看,

两者通过mknod/dev/abcc2450进行连接

9

复杂框架上层应用+驱动调用方法

9.1知识前期准备

9.1.1设备号的操作

设备号是由两部分组成的号,一个是主设备号,一个是次设备号。

操作的主要函数:

MKDEV();

MINOR();

MAJOR();

注册的两者方式:

动态注册,静态注册

1)、静态注册:

register_chrdev_region(dev_tfrom,unsignedcount,constchar*name)

2)、动态注册:

alloc_chrdev_region(dev_t*dev,unsignedbaseminor,unsignedcount,constchar*name)

设备号:

衔接应用空间到内核空间的符号

dev_t32bit

dev_tnum;--->得到主设备号:

MAJOR(XXX)XXX:

必须是主次设备号的一个整体

#defineMAJOR(dev)((unsignedint)((dev)>>MINORBITS))

#defineMINOR(dev)((unsignedint)((dev)&MINORMASK))

#defineMKDEV(ma,mi)(((ma)<

10复杂框架字符设备创建并注册过程

10.1完成基本模块框架

如:

module_init()module_exit()module_licence()头文件

10.2申请cdev结构体、申请file_operations结构体、创建设备号变量major

1)在文件开头创建设备号,模块代码中创建cdev变量和fops变量

2)

初始化fops结构体中的实现操作函数

10.3在函数中完成设备号注册与设备注册

1)使用 regester_chrdev_region()完成设备号注册,动态注册于静态注册两者,注意:

出错处理,设备号与整形数的转化MKDEV()

2)使用cdev_init()完成fops与cdev结构体绑定

3)使用cdev_add()完成内核的注册

10.4模块驱动的卸载

1)设备的注销

2)设备号的注销

*******************************************************************************

需要记住所有操作的函数名,不知用法可以使用sourceinsight搜索例子,照抄例子完成

见下图:

一个是设备号注册子系统,一个是操作子系统

11

file_operations常用函数

1)头文件

#include

#include

2)(*read)

对应上层的read函数

copy_to_user

copy_to_user(void__user*to,constvoid*from,unsignedlongn)

3)(*write)

对应上层的write函数

copy_from_user

copy_from_user(void*to,constvoid__user*from,unsignedlongn)

4)(*ioctl)

多用于设备控制,如lcd等

long(*unlocked_ioctl)(structfile*,unsignedint,unsignedlong);

int(*ioctl)(structinode*,structfile*,unsignedint,unsignedlong);

常用系统封装好的宏定义,完成命令的命名如下:

#defineWDIOC_GETSUPPORT_IOR(WATCHDOG_IOCTL_BASE,0,structwatchdog_info)

#defineWDIOC_GETSTATUS_IOR(WATCHDOG_IOCTL_BASE,1,int)

#defineWDIOC_GETBOOTSTATUS_IOR(WATCHDOG_IOCTL_BASE,2,int)

#defineWDIOC_GETTEMP_IOR(WATCHDOG_IOCTL_BASE,3,int)

#defineWDIOC_SETOPTIONS_IOR(WATCHDOG_IOCTL_BASE,4,int)

#define_IOR(type,nr,size)_IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))

#define_IOC(dir,type,nr,size)\

(((dir)<<_IOC_DIRSHIFT)|\

((type)<<_IOC_TYPESHIFT)|\

((nr)<<_IOC_NRSHIFT)|\

((size)<<_IOC_SIZESHIFT))

5)(*open)

设备的打开

int(*open)(structinode*,structfile*);

6)(*release)

对应上层的close

int(*release)(structinode*,structfile*);

7)(*fasync)

用于信号的操作

12

同步互斥操作

●同步互斥是为了解决临界资源访问问题

●就是控制一个资源谁先访问,谁后访问,并且只允许一个进程访问它,其它进程访问时等待。

●多用于多进程、多线程

12.1内核工作状态:

12.1.1抢占式内核和非抢占式内核区别

抢占式内核就是内核拥有多进程运行,并与应用程序共享cpu

非抢占式内核就是内核一个进程独享cpu,内核不返回,应用程序不执行。

12.1.2三种解决同步互斥的情况

1)、单CPU不具备抢占式的内核

但cpu,当cup执行操作某个资源时,不可能出现同时执行操作另一个进程操作这个资源,所以,进程上下文中,不需要考虑并发和互斥的问题

唯一可能出现该问题,只有中断。

解决办法:

开关中断:

local_irq_disable()------->local_irq_save(flags)

local_irq_enable()------->local_irq_restore(flags)

2)、单CPU具备抢占内核

进程上下文中,中断上下文都要考虑

解决办法:

同步互斥+开关中断

3)、多CPU

cpu可以同时操作某个资源,所以必须考虑同步互斥问题,并且中断和进程都要考虑

解决办法:

同步互斥+开关中断

13同步互斥函数总结

1)整型原子

2)自旋锁

3)信号量

13.1自旋锁

当等待时候执行while

(1),cpu一直占用,所以一般内部执行时间必须很短,如果没有解锁前又取锁,就会导致系统崩溃,所以使用时请慎行!

spinlock_tlock;定义

spin_lock_init(lock);初始化

spin_lock(lock);获得锁

spin_unlock(lock)释放锁

spin_lock_irq()=spin_lock()+local_irq_disable()

spin_unlock_irq()=spin_unlock()+local_irq_enable

spin_lock_irq()=spin_lock()+local_irq_save()

spin_unlock_irq()=spin_unlock()+local_irq_restore()

13.2信号量

structsemaphoresem;

sema_init()

DECLARE_MUTEX(name)完成两步:

初始化并设置初始值为1

DECLARE_MUTEX_LOCKED(name):

初始化并设置初始值为0

down(&sem);获得信号,消耗信号

up(&sem);释放信号

14阻塞IO编程流程

1、队列头结点的定义和初始化

2、人为的定义一个逻辑表达式作为我们的条件

3、wait_event(队列名)

唤醒

4、wake_up(队列名的地址)

1)DECLARE_WAIT_HEAD(NAME)定义初始化队列头

2)wait_event()

3)wait_up()

在全局声明DECLARE_WAIT_HEAD(my_wait)

●wait_event(my_wait,(i==0));当i==0的时候则运行(等到i==0的时候)

●wait_up(&my_wait)用于唤醒该队列

 

还有其它函数:

wait_event_interruptible(queue,conditon)可以中断唤醒

15轮询操作上层select下层poll

上层应用使用select下层内核调用poll,实现非阻塞方式,上层通过select访问内核子系统(内核有专门的子系统负责这功能),下次值需要将连个队列注册,则一旦有变化将mask放回内核子系统,再有内核子系统返回给上层的select函数fd。

unsignedint(*poll)(structfile*filp,structpoll_table*table);

主要结构:

staticunsignedintglobalfifo_poll(structfile*filp,poll_table*wait)

{

unsignedintmask=0;

structglobalfifo_dev*dev=filp->private_data;/*获得设备结构体指针*/

down(&dev->sem);

poll_wait(filp,&dev->r_wait,wait);

poll_wait(filp,&dev->w_wait,wait);

/*fifo非空*/

if(dev->current_len!

=0)

{

mask|=POLLIN|POLLRDNORM;/*标示数据可获得*/

}

/*fifo非满*/

if(dev->current_len!

=GLOBALFIFO_SIZE)

{

mask|=POLLOUT|POLLWRNORM;/*标示数据可写入*/

}

up(&dev->sem);

returnmask;

}

16信号处理

16.1上层应用:

#if1

fcntl(fd,F_SETOWN,getpid());

oflags=fcntl(fd,F_GETFL);

fcntl(fd,F_SETFL,oflags|FASYNC);即调用下层的globalfifo_fasync

#endif

#if0

fcntl(0,F_SETOWN,getpid());

oflags=fcntl(0,F_GETFL);

fcntl(0,F_SETFL,oflags|FASYNC);

16.2下层:

kill_fasync(&dev->async_queue,SIGIO,POLL_IN);

staticintglobalfifo_fasync(intfd,structfile*filp,intmode)

{

structglobalfifo_dev*dev=filp->private_data;

returnfasync_helper(fd,filp,mode,&dev->async_queue);

}

17中断

17.1中断简介

中断与内核调度的重要方式,中断可以抢cpu。

内核对每一个中断源,都使用structirqdescirq_desc结构体来描述,

每一个SOC芯片,都有很多的中断源,内核通过一个叫做NR_IRQS的宏定义这些中断源的数量

structirqdescirq_desc[NR_IRQS]

这个中断源不等于芯片厂商直接提供的中断源数量

S5PC100:

在内核中arch/arm/mach-s5pc100/include/mach/irqs.h这个文件描述了该SOC芯片在内核的中断子系统的源数量

17.2中断程序编写步骤和方式

17.2.1查找硬件你产生中断的引脚

由原理图找出key1在ENIT1上,然后找到该引脚对应的中断号,

17.2.2申请irq

●request_irq(unsignedintirq,irq_handler_thandler,unsignedlongflags,

constchar*name,void*dev)

●使用这个函数即可向内核子系统申请irq,注意参数的填写。

分别是。

irq号,irq处理函数,irq标志(有宏定义),可以看见的名称,传给处理函数的参数

注意:

中断号不是我们查得的中断号,是系统通过一种线性映射维护的一张中断向量表,我们去差,在内核源代码中:

/work/linux_kernel_copy/arch/arm/mach-s5pc100/include/mach/irqs.h(没有)

/work/linux_kernel_copy/arch/arm/plat-s5p/include/plat/irqs.h(找到)

root@ubuntu:

/work/linux_kernel_copy/include/linux#viinterrupt.h

irq_eint

(1)表示外部中断号1

17.2.3编写中断处理函数

staticirqreturn_tmy_irq(intirq,void*my_arg){

printk("irq_yes...=\n");

returnIRQ_HANDLED;}

17.2.4exit的时候释放中断

free_irq(IRQ_EINT

(1),NULL);

17.3具体编程实现截图

1)注册,在init里面注意:

flages

2)释放,在exit里面

3)中断服务程序注意返回值,

***********此时中断即可完成,但中断里面不能执行阻塞和循环锁,时间不能太长,所以中断有几种处理机制***************见书:

197

18中断新模型--上半部中断和下半部中断的实现

1,软中断和tasklet是中断处理机制,只是比紧急中断慢一点执行。

2,工作队列是将剩余的处理函数压入进程,与进程同级别受cpu调度。

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 幼儿教育 > 幼儿读物

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1