编译驱动原理
第一步:准备源代码
首先我们还是要来编写一个符合linux格式的模块文件,这样我们才能开始我们的模块编译。假设我们有一个源文件mymod.c。它的源码如下:
mymoles.c
1. #include <linux/mole.h> /* 引入与模块相关的宏 */
2. #include <linux/init.h> /* 引入mole_init() mole_exit()函数 */
3. #include <linux/moleparam.h> /* 引入mole_param() */
4
5. MODULE_AUTHOR("Yu Qiang");
6. MODULE_LICENSE("GPL");
7
8. static int nbr = 10;
9. mole_param(nbr, int, S_IRUGO);
10.
11. static int __init yuer_init(void)
12.{
13. int i;
14. for(i=0; i<nbr; i++)
15. {
16. printk(KERN_ALERT "Hello, How are you. %d/n", i);
17. }
18. return 0;
19.}
20.
21.static void __exit yuer_exit(void)
22.{
23. printk(KERN_ALERT"I come from yuer's mole, I have been unlad./n");
24.}
25.
26. mole_init(yuer_init);
27. mole_exit(yuer_exit);
我们的源文件就准备的差不多了,这就是一个linux下的模块的基本结构。第9行是导出我们的符号变量nbr。这样在你加载这个模块的时候可以动态修改这个变量的值。稍后将演示。yuer_init()函数将在模块加载的时候运行,通过输出的结果可以看到我们的模块是否加载成功。
第二步:编写Makefile文件
首先还是来看看我们Makefile的源文件,然后我们再来解释;
Makefile
obj-m := moles.o #要生成的模块名
moles-objs:= mymod.o #生成这个模块名所需要的目标文件
KDIR := /lib/moles/`uname -r`/build
PWD := $(shell pwd)
default:
make -C $(KDIR) M=$(PWD) moles
clean:
rm -rf *.o .* .cmd *.ko *.mod.c .tmp_versions
ARM平台
Makefile
obj-m += mymod.o
KDIR := /home/workspace2/kernel/linux-2.6.25 #如果是用于arm平台,则内核路径为arm内核的路径
PWD = $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) moles
clean:
rm -rf *.o
在arm板上插入是
insmod mymod
如果出现以下错误
insmod: chdir(/lib/moles): No such file or directory
则运行
mkdir /lib/moles/2.6.25 (与arm内核版本相同)
并将mymod.ko文件复制到该目录下
cp mymod.ko /lib/moles/2.6.25
然后再执行 (insmod 只在/lib/moles/2.6.25目录下查找相关驱动模块)
insmod mymod
现在我来说明一下这个Makefile。请记住是大写的Makefile而不是小写的makefile;
obj-m :这个变量是指定你要声称哪些模块模块的格式为 obj-m := <模块名>.o
moles-objs :这个变量是说明声称模块moles需要的目标文件 格式要求 <模块名>-objs := <目标文件>
切记:模块的名字不能取与目标文件相同的名字。如在这里模块名不能取成 mymod;
KDIR :这是我们正在运行的操作系统内核编译目录。也就是编译模块需要的环境
M= :指定我们源文件的位置
PWD :这是当前工作路径$(shell )是make的一个内置函数。用来执行shell命令。
第三步:编译模块
现在我们已经准备好了我们所需要的源文件和相应的Makefile。我们现在就可以编译了。在终端进入源文件目录输入make
运行结果:
make[1]: Entering directory `/usr/src/linux-headers-2.6.24-24-generic'
CC [M] /home/yuqiang/桌面/mymole/mymoles.o
LD [M] /home/yuqiang/桌面/mymole/moles.o
Building moles, stage 2.
MODPOST 1 moles
CC /home/yuqiang/桌面/mymole/moles.mod.o
LD [M] /home/yuqiang/桌面/mymole/moles.ko
make[1]: Leaving directory `/usr/src/linux-headers-2.6.24-24-generic'
第四步:加载/卸载我们的模块
从上面的编译中我可以看到。已经有一个moles.ko生成了。这就是我们的模块了。现在我们就可以来加载了。
首先在终端输入:sudo insmod moles.ko
现在我们来看看我们的模块加载成功没有呢?
在终端输入:dmesg | tail -12 这是查看内核输出信息的意思。tail -12 显示最后12条;
显示结果如下:
[17945.024417] sd 9:0:0:0: Attached scsi generic sg2 type 0
[18046.790019] usb 5-8: USB disconnect, address 9
[19934.224812] Hello, How are you. 0
[19934.224817] Hello, How are you. 1
[19934.224818] Hello, How are you. 2
[19934.224820] Hello, How are you. 3
[19934.224821] Hello, How are you. 4
[19934.224822] Hello, How are you. 5
[19934.224824] Hello, How are you. 6
[19934.224825] Hello, How are you. 7
[19934.224826] Hello, How are you. 8
[19934.224828] Hello, How are you. 9
看到了吧。我们的模块的初始化函数yuer_init();已经成功运行了。说明我们的模块已经加载成功;
现在我们再来卸载模块试试看。
在终端输入:sudo rmmod moles
在终端输入:dmesg | tail -3
[19934.224826] Hello, How are you. 8
[19934.224828] Hello, How are you. 9
[20412.046932] I come from yuer's mole, I have been unlad.
可以从打印的信息中看到,我们的模块的退出函数已经被执行了。说明我们的模块已经被成功的卸载了。到目前位置我们就已经算是对模块的编译到编译运行算是有了一个整体上的认识了。对于以后深入的学习还是应该有点帮助的。下面我们将在看看于模块相关的一些简单的操作。
第五步:加载模块时传递参数
在终端输入:sudo insmod mole_name.ko nbr=4
在终端输入:dmesg | tail -6
显示结果如下:
[20800.655694] Hello, How are you. 9
[21318.675593] I come from onefile mole, I have been unlad.
[21334.425373] Hello, How are you. 0
[21334.425378] Hello, How are you. 1
[21334.425380] Hello, How are you. 2
[21334.425381] Hello, How are you. 3
这样我们就可以看到在模块加载的时候动态设置了我们的一个变量。初始化函数中的循环只执行了4次。
可能你会问我怎么知道一个模块可以设置那些变量呢。当然,你可以先不设变量加载一次。然后可以在终端输入ls /sys/mole/<moles_name>/parameters/来查看。在这里我们是这样输入的
在终端输入:ls /sys/moedle/moles/parameters/
显示结果:
nbr
如果我们的模块加载成功了。最后我们还可以通过modinfo来查看我们的模块信息。如下
在终端输入:sudo modinfo moles.ko
显示结果:
filename: moles.ko
license: GPL
author: Yu Qiang
srcversion: 20E9C3C4E02D130E6E92533
depends:
vermagic: 2.6.24-24-generic SMP mod_unload 586
parm: nbr:int
⑵ linux下怎么编译安装驱动
linux 编译安装驱动有两种,动态加载与静态加载
动态加载
一,编译,在指点内核树下编译,生成.o文件或.ko文件
二,将生成的.o或.ko文件拷到相应目录,一般是/lib/mole/kernel下面
三,用insmod命令加载,用rmmod命令卸载
静态加载
静态加载主要就是编译内核。就是将编写好的驱动放进内核相应的目录下面。然后编译内核。然后运行编译好的内核。
⑶ 驱动的原理是什么
驱动是一种软件,功能是建立操作系统和硬件之间的桥梁。
主板上败吵空的接口、控制器这些都是硬件能够实现操作系统让它干的功能。
操作系统给下层硬件发功能指令的时候是不关心底层硬件的具体实现方式的。
驱动的原理就是将底层硬件包装成操作系统能统一调配的功能集
原理图:
硬件(电路级实现)<-------- 驱动程序(转换)<--------- 操作察瞎系统(功能原语)
驱动的目的就是屏蔽各种底层硬件之间的差异,隐藏技术细节,给上层软件提供一个看上去是一样的功能集。
不同硬件对操作系统功能的具体实现方式是不一样的,所以不同硬件有着不同的驱动。
同一厂商,尤其是统一系列的产碰桐品,因为实现方式大多类似,故其驱动能在一定范围内通用。
纯手打,希望有帮助
⑷ Linux驱动程序的工作原理
由于你的问题太长我只好转载别人的手打的太累不好意思~~~
Linux是Unix***作系统的一种变种,在Linux下编写驱动程序的原理和
思想完全类似于其他的Unix系统,但它dos或window环境下的驱动程序有很大的
区别.在Linux环境下设计驱动程序,思想简洁,***作方便,功芤端口芮看?但是
支持函数少,只能依赖kernel中的函数,有些常用的***作要自己来编写,而且调
试也不方便.本人这几周来为实验室自行研制的一块多媒体卡编制了驱动程序,
获得了一些经验,愿与Linux fans共享,有不当之处,请予指正.
以下的一些文字主要来源于khg,johnsonm的Write linux device driver,
Brennan's Guide to Inline Assembly,The Linux A-Z,还有清华BBS上的有关
device driver的一些资料. 这些资料有的已经过时,有的还有一些错误,我依
据自己的试验结果进行了修正.
一. Linux device driver 的概念
系统调用是***作系统内核和应用程序之间的接口,设备驱动程序是***作系统
内核和机器硬件之间的接口.设备驱动程序为应用程序屏蔽了硬件的细节,这样
在应用程序看来,硬件设备只是一个设备文件, 应用程序可以象***作普通文件
一样对硬件设备进行***作.设备驱动程序是内核的一部分,它完成以下的功能:
1.对设备初始化和释放.
2.把数据从内核传送到硬件和从硬件读取数据.
3.读取应用程序传送给设备文件的数据和回送应用程序请求的数据.
4.检测和处理设备出现的错误.
在Linux***作系统下有两类主要的设备文件类型,一种是字符设备,另一种是
块设备.字符设备和块设备的主要区别是:在对字符设备发出读/写请求时,实际
的硬件I/O一般就紧接着发生了,块设备则不然,它利用一块系统内存作缓冲区,
当用户进程对设备请求能满足用户的要求,就返回请求的数据,如果不能,就调用请求函数来进行实际
的I/O***作.块设备是主要针对磁盘等慢速设备设计的,以免耗费过多的CPU时间
来等待.
已经提到,用户进程是通过设备文件来与实际的硬件打交道.每个设备文件都
都有其文件属性(c/b),表示是字符设备还蔤强樯璞?另外每个文件都有两个设
备号,第一个是主设备号,标识驱动程序,第二个是从设备号,标识使用同一个
设备驱动程序的不同的硬件设备,比如有两个软盘,就可以用从设备号来区分
他们.设备文件的的主设备号必须与设备驱动程序在登记时申请的主设备号
一致,否则用户进程将无法访问到驱动程序.
最后必须提到的是,在用户进程调用驱动程序时,系统进入核心态,这时不再是
抢先式调度.也就是说,系统必须在你的驱动程序的子函数返回后才能进行其他
的工作.如果你的驱动程序陷入死循环,不幸的是你只有重新启动机器了,然后就
是漫长的fsck.//hehe
(请看下节,实例剖析)
读/写时,它首先察看缓冲区的内容,如果缓冲区的数据
如何编写Linux***作系统下的设备驱动程序
Roy G
二.实例剖析
我们来写一个最简单的字符设备驱动程序.虽然它什么也不做,但是通过它
可以了解Linux的设备驱动程序的工作原理.把下面的C代码输入机器,你就会
获得一个真正的设备驱动程序.不过我的kernel是2.0.34,在低版本的kernel
上可能会出现问题,我还没测试过.//xixi
#define __NO_VERSION__
#include
#include
char kernel_version [] = UTS_RELEASE;
这一段定义了一些版本信息,虽然用处不是很大,但也必不可少.Johnsonm说所
有的驱动程序的开头都要包含,但我看倒是未必.
由于用户进程是通过设备文件同硬件打交道,对设备文件的***作方式不外乎就
是一些系统调用,如 open,read,write,close...., 注意,不是fopen, fread.,
但是如何把系统调用和驱动程序关联起来呢?这需要了解一个非常关键的数据
结构:
struct file_operations {
int (*seek) (struct inode * ,struct file *, off_t ,int);
int (*read) (struct inode * ,struct file *, char ,int);
int (*write) (struct inode * ,struct file *, off_t ,int);
int (*readdir) (struct inode * ,struct file *, struct dirent * ,int);
int (*select) (struct inode * ,struct file *, int ,select_table *);
int (*ioctl) (struct inode * ,struct file *, unsined int ,unsigned long
int (*mmap) (struct inode * ,struct file *, struct vm_area_struct *);
int (*open) (struct inode * ,struct file *);
int (*release) (struct inode * ,struct file *);
int (*fsync) (struct inode * ,struct file *);
int (*fasync) (struct inode * ,struct file *,int);
int (*check_media_change) (struct inode * ,struct file *);
int (*revalidate) (dev_t dev);
}
这个结构的每一个成员的名字都对应着一个系统调用.用户进程利用系统调用
在对设备文件进行诸如read/write***作时,系统调用通过设备文件的主设备号
找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制
权交给该函数.这是linux的设备驱动程序工作的基本原理.既然是这样,则编写
设备驱动程序的主要工作就是编写子函数,并填充file_operations的各个域.
相当简单,不是吗?
下面就开始写子程序.
#include
#include
#include
#include
#include
unsigned int test_major = 0;
static int read_test(struct inode *node,struct file *file,
char *buf,int count)
{
int left;
if (verify_area(VERIFY_WRITE,buf,count) == -EFAULT )
return -EFAULT;
for(left = count left > 0 left--)
{
__put_user(1,buf,1);
buf++;
}
return count;
}
这个函数是为read调用准备的.当调用read时,read_test()被调用,它把用户的
缓冲区全部写1.
buf 是read调用的一个参数.它是用户进程空间的一个地址.但是在read_test
被调用时,系统进入核心态.所以不能使用buf这个地址,必须用__put_user(),
这是kernel提供的一个函数,用于向用户传送数据.另外还有很多类似功能的
函数.请参考.在向用户空间拷贝数据之前,必须验证buf是否可用.
这就用到函数verify_area.
static int write_tibet(struct inode *inode,struct file *file,
const char *buf,int count)
{
return count;
}
static int open_tibet(struct inode *inode,struct file *file )
{
MOD_INC_USE_COUNT;
return 0;
} static void release_tibet(struct inode *inode,struct file *file )
{
MOD_DEC_USE_COUNT;
}
这几个函数都是空***作.实际调用发生时什么也不做,他们仅仅为下面的结构
提供函数指针。
struct file_operations test_fops = {
NULL,
read_test,
write_test,
NULL, /* test_readdir */
NULL,
NULL, /* test_ioctl */
NULL, /* test_mmap */
open_test,
release_test, NULL, /* test_fsync */
NULL, /* test_fasync */
/* nothing more, fill with NULLs */
};
设备驱动程序的主体可以说是写好了。现在要把驱动程序嵌入内核。驱动程序
可以按照两种方式编译。一种是编译进kernel,另一种是编译成模块(moles),
如果编译进内核的话,会增加内核的大小,还要改动内核的源文件,而且不能
动态的卸载,不利于调试,所以推荐使用模块方式。
int init_mole(void)
{
int result;
result = register_chrdev(0, "test", &test_fops);
if (result < 0) {
printk(KERN_INFO "test: can't get major number ");
return result;
}
if (test_major == 0) test_major = result; /* dynamic */
return 0;
}
在用insmod命令将编译好的模块调入内存时,init_mole 函数被调用。在
这里,init_mole只做了一件事,就是向系统的字符设备表登记了一个字符
设备。register_chrdev需要三个参数,参数一是希望获得的设备号,如果是
零的话,系统将选择一个没有被占用的设备号返回。参数二是设备文件名,
参数三用来登记驱动程序实际执行***作的函数的指针。
如果登记成功,返回设备的主设备号,不成功,返回一个负值。
void cleanup_mole(void)
{
unregister_chrdev(test_major, "test");
}
在用rmmod卸载模块时,cleanup_mole函数被调用,它释放字符设备test
在系统字符设备表中占有的表项。
一个极其简单的字符设备可以说写好了,文件名就叫test.c吧。
下面编译
$ gcc -O2 -DMODULE -D__KERNEL__ -c test.c
得到文件test.o就是一个设备驱动程序。
如果设备驱动程序有多个文件,把每个文件按上面的命令行编译,然后
ld -r file1.o file2.o -o molename.
驱动程序已经编译好了,现在把它安装到系统中去。
$ insmod -f test.o
如果安装成功,在/proc/devices文件中就可以看到设备test,
并可以看到它的主设备号,。
要卸载的话,运行
$ rmmod test
下一步要创建设备文件。
mknod /dev/test c major minor
c 是指字符设备,major是主设备号,就是在/proc/devices里看到的。
用shell命令
$ cat /proc/devices | awk "\$2=="test" {print \$1}"
就可以获得主设备号,可以把上面的命令行加入你的shell script中去。
minor是从设备号,设置成0就可以了。
我们现在可以通过设备文件来访问我们的驱动程序。写一个小小的测试程序。
#include
#include
#include
#include
main()
{
int testdev;
int i;
char buf[10];
testdev = open("/dev/test",O_RDWR);
if ( testdev == -1 )
{
printf("Cann't open file ");
exit(0);
}
read(testdev,buf,10);
for (i = 0; i < 10;i++)
printf("%d ",buf);
close(testdev);
}
编译运行,看看是不是打印出全1 ?
以上只是一个简单的演示。真正实用的驱动程序要复杂的多,要处理如中断,
DMA,I/O port等问题。这些才是真正的难点。请看下节,实际情况的处理。
如何编写Linux***作系统下的设备驱动程序
Roy G
三 设备驱动程序中的一些具体问题。
1. I/O Port.
和硬件打交道离不开I/O Port,老的ISA设备经常是占用实际的I/O端口,
在linux下,***作系统没有对I/O口屏蔽,也就是说,任何驱动程序都可以
对任意的I/O口***作,这样就很容易引起混乱。每个驱动程序应该自己避免
误用端口。
有两个重要的kernel函数可以保证驱动程序做到这一点。
1)check_region(int io_port, int off_set)
这个函数察看系统的I/O表,看是否有别的驱动程序占用某一段I/O口。
参数1:io端口的基地址,
参数2:io端口占用的范围。
返回值:0 没有占用, 非0,已经被占用。
2)request_region(int io_port, int off_set,char *devname)
如果这段I/O端口没有被占用,在我们的驱动程序中就可以使用它。在使用
之前,必须向系统登记,以防止被其他程序占用。登记后,在/proc/ioports
文件中可以看到你登记的io口。
参数1:io端口的基地址。
参数2:io端口占用的范围。
参数3:使用这段io地址的设备名。
在对I/O口登记后,就可以放心地用inb(), outb()之类的函来访问了。
在一些pci设备中,I/O端口被映射到一段内存中去,要访问这些端口就相当
于访问一段内存。经常性的,我们要获得一块内存的物理地址。在dos环境下,
(之所以不说是dos***作系统是因为我认为DOS根本就不是一个***作系统,它实
在是太简单,太不安全了)只要用段:偏移就可以了。在window95中,95ddk
提供了一个vmm 调用 _MapLinearToPhys,用以把线性地址转化为物理地址。但
在Linux中是怎样做的呢?
2 内存***作
在设备驱动程序中动态开辟内存,不是用malloc,而是kmalloc,或者用
get_free_pages直接申请页。释放内存用的是kfree,或free_pages. 请注意,
kmalloc等函数返回的是物理地址!而malloc等返回的是线性地址!关于
kmalloc返回的是物理地址这一点本人有点不太明白:既然从线性地址到物理
地址的转换是由386cpu硬件完成的,那样汇编指令的***作数应该是线性地址,
驱动程序同样也不能直接使用物理地址而是线性地址。但是事实上kmalloc
返回的确实是物理地址,而且也可以直接通过它访问实际的RAM,我想这样可
以由两种解释,一种是在核心态禁止分页,但是这好像不太现实;另一种是
linux的页目录和页表项设计得正好使得物理地址等同于线性地址。我的想法
不知对不对,还请高手指教。
言归正传,要注意kmalloc最大只能开辟128k-16,16个字节是被页描述符
结构占用了。kmalloc用法参见khg.
内存映射的I/O口,寄存器或者是硬件设备的RAM(如显存)一般占用F0000000
以上的地址空间。在驱动程序中不能直接访问,要通过kernel函数vremap获得
重新映射以后的地址。
另外,很多硬件需要一块比较大的连续内存用作DMA传送。这块内存需要一直
驻留在内存,不能被交换到文件中去。但是kmalloc最多只能开辟128k的内存。
这可以通过牺牲一些系统内存的方法来解决。
具体做法是:比如说你的机器由32M的内存,在lilo.conf的启动参数中加上
mem=30M,这样linux就认为你的机器只有30M的内存,剩下的2M内存在vremap
之后就可以为DMA所用了。
请记住,用vremap映射后的内存,不用时应用unremap释放,否则会浪费页表。
3 中断处理
同处理I/O端口一样,要使用一个中断,必须先向系统登记。
int request_irq(unsigned int irq ,
void(*handle)(int,void *,struct pt_regs *),
unsigned int long flags,
const char *device);
irq: 是要申请的中断。
handle:中断处理函数指针。
flags:SA_INTERRUPT 请求一个快速中断,0 正常中断。
device:设备名。
如果登记成功,返回0,这时在/proc/interrupts文件中可以看你请求的
中断。
4一些常见的问题。
对硬件***作,有时时序很重要。但是如果用C语言写一些低级的硬件***作
的话,gcc往往会对你的程序进行优化,这样时序就错掉了。如果用汇编写呢,
gcc同样会对汇编代码进行优化,除非你用volatile关键字修饰。最保险的
办法是禁止优化。这当然只能对一部分你自己编写的代码。如果对所有的代码
都不优化,你会发现驱动程序根本无法装载。这是因为在编译驱动程序时要
用到gcc的一些扩展特性,而这些扩展特性必须在加了优化选项之后才能体现
出来。
关于kernel的调试工具,我现在还没有发现有合适的。有谁知道请告诉我,
不胜感激。我一直都在printk打印调试信息,倒也还凑合。
关于设备驱动程序还有很多内容,如等待/唤醒机制,块设备的编写等。
我还不是很明白,不敢乱说。
⑸ 高手进阶:Linux操作系统驱动编译与运行
一、手工加载测试
1、insmod
./key_test.ko
加载驱动模块到内核
2、cat
/proc/moles
|grep
key_test
查看key_test模块在内核中的地址,不加过滤器可以看到全部加载的模块。
3、lsmod
显示模块,这时可以看到所有的模块名字,后面跟的是主设备号和次设备号。
4、rmmod
key_test
把模块从内核里卸载。
二、动态加载
1、把key_test.c源代码放到内核源代码的/drives/char/下,因为这是属字符型驱动,放在这编译到zImage中。
2、这时我们make
menuconfig
编译内核是看不到key_test这个选项的。我们把这个选项写到菜单里面才行。在内核源代码的/drives/char/下有一个Kconfig文件,打开
(1)
vi
Kconfig
加几行到里面:
config
ConFig_key_test
bool
"key
test"
//前面那个bool换成tristate就是支持模块化编译
上面句是在make
menuconfig时会出现key
test这个选项在drive/char子菜单下,bool前面是TAB键
------help----------
这句是出现在菜单选项下面的
This
key
test
help.
这句是你的驱动的说明会出现在help里面
(2)在/drivers/char目录下的Makefile文件里加上一句:
obj-$(CONFIG_key_test)
+=
key_test.o
上面这句是让Make时把key_test编译到内核中.
(3)
make
menuconfig
把key_test选项选取上
(4)
make
zImage
生成zImage文件,重启动加载这个新编的内核。
3、lsmod就能看到key_test了,但是还不能用,没有接口,也就是/dev下面没有
4、mknod
/dev/key_test
c
121
0
这是创建设备到/dev下,使普通程序可以调用了,121是在源代码里定义的它的主设备号,0是次设备号。
5、cat
/dev/key_test
这是相当于open这个设备了,或者写一个程序直接调用open、write等函数。
fd=("/dev/key_test",ORW);
⑹ 如何编译驱动程序
驱动的编译和上层应用程序的编译完全不同,作为初学者应该先了解一下,即使你还不懂得怎么写驱动程序。
首先安装DDK,然后随便找一个例子来测试。在菜单中找到BUILD环境菜单执行,不同的系统要使用不同的BUILD环境。会打开一个DOS窗口,这时CD到那个例子程序,输入 build –cZ回车就可以了。 驱动程序都是用一个由DDK提供的叫build.exe的工具编译的。此程序以一个名为SOURCES的文件作为输入,该文件中包含目标可执行文件的名称、类型和要创建的可执行文件的路径,注意这个文件没有后缀名。
SOURCES的文件格式:
TARGETNAME=drivername ,
- 本参数用于指定生成的设备驱动程序名称(不需后缀名),所产生的文件
- 为drivername.sys.
TARGETPATH=./lib
- 本参数用于指定生成的设备驱动程序所存放的路径. 一般采用./lib.
TARGETTYPE=DRIVER
- build能够生成许多不同的目标对象,设备驱动程序一般选用 DRIVER.
INCLUDES=path1;path2;...
- 本参数是可选的, 用于指定其他的#include文件的搜索路径.
TARGETLIBS=lib1;lib2;...
- 本参数是可选的, 用于指定其他的lib库文件的搜索路径.
SOURCES=file1.c file2.c ...
- 本参数用于指定需被编译的全部源文件名称, 后缀名不能省略,文件名之间用空格分开.
SOURCES文件是必需的,如果没有它则表示没有任何源文件需要编译。
如果要换行可以用 ‘/’ 符号,表示对上一行的继续。
也可以创建DIRS文件,DIRS文件用于指定在当前目录下必须创建的子目录。
DIRS文件格式:
DIRS文件的内容由一系列用空格分开的目录名组成
DIRS = /
subdir1 /
subdir2 /
subdir3
DIRS文件是可选的。
有的时候,会提示找不到依赖的文件(.h,.lib 之类),其实设置好 source 文件的
INCLUDES和TARGETLIBS就可以,我第一次编译时就碰到这个问题,和VC环境区别较大,但习惯就好。
⑺ linux驱动程序结构框架及工作原理分别是什么
一、Linux device driver 的概念x0dx0ax0dx0a系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作。设备驱动程序是内核的一部分,它完成以下的功能:x0dx0ax0dx0a1、对设备初始化和释放;x0dx0ax0dx0a2、把数据从内核传送到硬件和从硬件读取数据;x0dx0ax0dx0a3、读取应用程序传送给设备文件的数据和回送应用程序请求的数据;x0dx0ax0dx0a4、检测和处理设备出现的错误。x0dx0ax0dx0a在Linux操作系统下有三类主要的设备文件类型,一是字符设备,二是块设备,三是网络设备。字符设备和块设备的主要区别是:在对字符设备发出读/写请求时,实际的硬件I/O一般就紧接着发生了,块设备则不然,它利用一块系统内存作缓冲区,当用户进程对设备请求能满足用户的要求,就返回请求的数据,如果不能,就调用请求函数来进行实际的I/O操作。块设备是主要针对磁盘等慢速设备设计的,以免耗费过多的CPU时间来等待。x0dx0ax0dx0a已经提到,用户进程是通过设备文件来与实际的硬件打交道。每个设备文件都都有其文件属性(c/b),表示是字符设备还是块设备?另外每个文件都有两个设备号,第一个是主设备号,标识驱动程序,第二个是从设备号,标识使用同一个设备驱动程序的不同的硬件设备,比如有两个软盘,就可以用从设备号来区分他们。设备文件的的主设备号必须与设备驱动程序在登记时申请的主设备号一致,否则用户进程将无法访问到驱动程序。x0dx0ax0dx0a最后必须提到的是,在用户进程调用驱动程序时,系统进入核心态,这时不再是抢先式调度。也就是说,系统必须在你的驱动程序的子函数返回后才能进行其他的工作。如果你的驱动程序陷入死循环,不幸的是你只有重新启动机器了,然后就是漫长的fsck。x0dx0ax0dx0a二、实例剖析x0dx0ax0dx0a我们来写一个最简单的字符设备驱动程序。虽然它什么也不做,但是通过它可以了解Linux的设备驱动程序的工作原理。把下面的C代码输入机器,你就会获得一个真正的设备驱动程序。x0dx0ax0dx0a由于用户进程是通过设备文件同硬件打交道,对设备文件的操作方式不外乎就是一些系统调用,如 open,read,write,close?, 注意,不是fopen, fread,但是如何把系统调用和驱动程序关联起来呢?这需要了解一个非常关键的数据结构:x0dx0ax0dx0aSTruct file_operatiONs {x0dx0ax0dx0aint (*seek) (struct inode * ,struct file *, off_t ,int);x0dx0ax0dx0aint (*read) (struct inode * ,struct file *, char ,int);x0dx0ax0dx0aint (*write) (struct inode * ,struct file *, off_t ,int);x0dx0ax0dx0aint (*readdir) (struct inode * ,struct file *, struct dirent * ,int);x0dx0ax0dx0aint (*select) (struct inode * ,struct file *, int ,select_table *);x0dx0ax0dx0aint (*ioctl) (struct inode * ,struct file *, unsined int ,unsigned long);x0dx0ax0dx0aint (*mmap) (struct inode * ,struct file *, struct vm_area_struct *);x0dx0ax0dx0aint (*open) (struct inode * ,struct file *);x0dx0ax0dx0aint (*release) (struct inode * ,struct file *);x0dx0ax0dx0aint (*fsync) (struct inode * ,struct file *);x0dx0ax0dx0aint (*fasync) (struct inode * ,struct file *,int);x0dx0ax0dx0aint (*check_media_change) (struct inode * ,struct file *);x0dx0ax0dx0aint (*revalidate) (dev_t dev);x0dx0ax0dx0a}x0dx0ax0dx0a这个结构的每一个成员的名字都对应着一个系统调用。用户进程利用系统调用在对设备文件进行诸如read/write操作时,系统调用通过设备文件的主设备号找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数。这是linux的设备驱动程序工作的基本原理。既然是这样,则编写设备驱动程序的主要工作就是编写子函数,并填充file_operations的各个域。x0dx0ax0dx0a下面就开始写子程序。x0dx0ax0dx0a#include
⑻ 如何编译一个linux下的驱动模块
首先,我们要了解一下模块是如何别被构造的。模块的构造过程与用户空间
的应用程序的构造过程有显着不同;内核是一个大的、独立的程序
,
对于它的各
个部分如何组合在一起有详细的明确的要求。
Linux2.6
内核的构造过程也与以
前版本的内核构造过程不同;
新的构造系统用起来更加简单,
并且可产生更加正
确的结果
,
但是它看起来和先前的方法有很大不同。内核的构造系统非常复杂
,
我们所看到的只是它的一小部分。
如果读者想了解更深入的细节,
则应阅读在内
核源码中的
Document/kbuild
目录下的文件。
在构造内核模块之前,
有一些先决条件首先应该得到满足。
首先,
读者要保证你
有适合于你的内核版本的编译器、模块工具
,
以及其他必要工具。在内核文档目
录下的文件
Documentation/Changes
里列出了需要的工具版本;
在开始构造内
核前,
读者有必要查看该文件,
并确保已安装了正确的工具。
如果用错误的工具
版本来构造一个内核
(
及其模块
)
,可能导致许多奇怪的问题。另外也要注意
,
使
用太新版本的编译器偶尔可能也会导致问题。
一旦做好了上面的准备工作之后
,
其实给自己的模块创建一个
makefile
则非常
简单。实际上
,
对于本章前面展示的
" hello world"
例子
,
下面一行就够了
:
obj-m := hello.o
如果读者熟悉
make
,
但是对
Linux2.6
内核构造系统不熟悉的话
,
可能奇怪这个
makefile
如何工作。毕竟上面的这一行不是一个传统的
makefile
的样子。问
题的答案当然是内核构造系统处理了余下的工作。上面的赋值语句
(
它利用了由
GNU make
提供的扩展语法
)
说明有一个模块要从目标文件
hello.o
构造,而从
该目标文件构造的模块名称为
hello.ko.
如果我们想由两个源文件
(
比如
file1.c
和
file2.c )
构造出一个名称为
mole.ko
的模块
,
则正确的
makefile
可如下编写
:
obj-m := mole.o
mole-objs := file1.o file2.o
为了让上面这种类型的
makefile
文件正常工作
,
必须在大的内核构造系统环境
中调用他们。假设读者的内核源码数位于
~/kernel-2.6
目录
,
用来建立你的模
块的
make
命令
(
在包含模块源代码和
makefile
的目录下键入
)
应该是
:
make -C ~/kernel-2.6 M=`pwd` moles
这个命令首先是改变目录到用
-C
选项指定的位置
(
即内核源代码目录
)
,其中保
存有内核的顶层
makefile
文件。这个
M=
选项使
makefile
在构造
moles
目
标前
,
返回到模块源码目录。
然后,
moles
目标指向
obj-m
变量中设定的模块,
在上面的例子里,我们将该变量设置成了
mole.o
。
上面这样的
make
命令对于多个文件的编译显得不是很方便
,
于是内核开发者就
开发了一种
makefile
方式
,
这种方式使得内核树之外的模块构造变得更加容易。
代码清单
1.4
展示了
makefile
的编写方法:
代码清单
1.4 makefile
ifeq ($(KERNELRELEASE),)
KERNELDIR ?= /source/linux-2.6.13
PWD := $(shell pwd)
moles:
$(MAKE) -C $(KERNELDIR) M=$(PWD) moles
moles_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) moles_install
clean:
rm -rf *.o *~ core .depend .*. *.ko *.mod.c .tmp_versions
.PHONY: moles moles_install clean
else
obj-m := hello.o
endif
我们再次看到了扩展的
GNU
make
语法在起作用。在一个典型的构造过程中,这
个
makefile
将被读取两次。当从命令行中调用这个
makefile ,
它注意到
KERNELRELEASE
变量尚未设置。我们可以注意到,已安装的模块目录中存在一
个符号连接,
它指向内核的构造树,
这样这个
makefile
就可以定位内核的源代
码目录。如果读者时间运行的内核并不是要构造的内核,则可以在命令行提供
KERNELDIR=
选项或者设置
KERNELDIR
环境变量
,
或者修改
makefile
中设置
KERNELDIR
的那一行。在找到内核源码树
,
这个
makefile
会调用
default:
目
标
,
这个目标使用先前描述过的方法第二次运行
make
命令
(
注意,在这个
makefile
里
make
命令被参数化成
$(MAKE))
,以便运行内核构造系统。在第二
次读取
makefile
时,
它设置了
obj-m,
而内核的
makefile
负责真正构造模块。
这种构造模块的机制看起来很繁琐,可是,一旦我们习惯了使用这种机制
,
则会
欣赏内核构造系统带给我们的便利。需要注意的是
,
上面
makefile
并不完整,
一个真正的
makefile
应包含通常用来清除无用文件的目标
,
安装模块的目标等
等。一个完整的例子可以参考例子代码目录的
makefile
。
⑼ 软件驱动硬件的原理是什么
驱动程序即添加到操作系统中的一小块代码,其中包含有关硬件设备的信息。有了此信息,计算机就可以与设备进行通信。驱动程序是硬件厂商根据操作系统编写的配置文件,可以说没有驱动程序,计算机中的硬件就无法工作。操作系统不同,硬件的驱动程序也不同,各个硬件厂商为了保证硬件的兼容性及增强硬件的功能会不断地升级驱动程序。如:Nvidia 显卡芯片公司平均每个月会升级显卡驱动程序2-3次。驱动程序是硬件的一部分,当你安装新硬件时,驱动程序是一项不可或缺的重要元件。凡是安装一个原本不属于你电脑中的硬件设备时,系统就会要求你安装驱动程序,将新的硬件与电脑系统连接起来。驱动程序扮演沟通的角色,把硬件的功能告诉电脑系统,并且也将系统的指令传达给硬件,让它开始工作。 x0dx0a当你在安装新硬件时总会被要求放入“这种硬件的驱动程序”,很多人这时就开始头痛。不是找不到驱动程序的盘片,就是找不到文件的位置,或是根本不知道什么是驱动程序。比如安装打印机这类的硬件外设,并不是把连接线接上就算完成,如果你这时候开始使用,系统会告诉你,找不到驱动程序。怎么办呢?参照说明书也未必就能顺利安装。其实在安装方面还是有一定的惯例与通则可寻的,这些都可以帮你做到无障碍安装。 x0dx0a在Windows系统中,需要安装主板、光驱、显卡、声卡等一套完整的驱动程序。如果你需要外接别的硬件设备,则还要安装相应的驱动程序,如:外接游戏硬件要安装手柄、方向盘、摇杆、跳舞毯等的驱动程序,外接打印机要安装打印机驱动程序,上网或接入局域网要安装网卡、Moden甚至ISDN、ADSL的驱动程序。说了这么多的驱动程序,你是否有一点头痛了。下面就介绍Windows系统中各种的不同硬件设备的驱动程序,希望能让你拨云见日。 x0dx0a在Windows 9x下,驱动程序按照其提供的硬件支持可以分为:声卡驱动程序、显卡驱动程序、鼠标驱动程序碰稿昌、主板驱动程序、网络设备驱动程序、打印机驱动程序、扫描仪驱动程序等等。为什么没有CPU、内存驱动程序呢?因为CPU和内存无需驱动程序便可使用,不仅如此,绝大多数键盘、鼠标、硬盘、软驱、显示器和主板上的标准设备都可以用Windows自带的标准驱动程序来驱动,当然其它特定功能除外。如果敬碰你需要在Windows系统中的DOS模式下使用光驱,那么还需要在DOS模式下安装光驱驱动程序。多数显卡、声卡、网卡等内置扩展卡和打印机、扫描仪、外置Modem等外设都需要安装与设备型号相符的驱动程序,否则无法发挥其部分或全部功能。驱动程序一般可通过三种途径得到,一是购买的硬件附带有驱动程序;二是Windows系统自带有大量驱动程序;三是从Internet下载驱动程序。最后一种途径往往能够得到最新的驱动程序。 x0dx0a供Windows 9x使用的驱动程序包通常由一些.vxd(或.386)、.drv、.sys、.dll或.exe等文件组成,在安装过程中,大部分文件都会被拷贝到“Windows\ System”目录下。 x0dx0aWindows怎样知道安装的是什么设备,以及要拷贝哪些文件呢?答案在于.inf文件。.inf是从Windows 95时代开始引入的一种描述设备安装信息的文件,它用特定语法的文字来说明要安装的设备类型、生产厂商、型号、要拷贝笑扒的文件、拷贝到的目标路径,以及要添加到注册表中的信息。通过读取和解释这些文字,Windows便知道应该如何安装驱动程序。目前几乎所有硬件厂商提供的用于Windows 9x下的驱动程序都带有安装信息文件。事实上,.inf文件不仅可用于安装驱动程序,还能用来安装与硬件并没有什么关系的软件,例如Windows 98支持“Windows更新”功能,更新时下载的系统部件就是利用.inf文件来说明如何安装该部件的。 x0dx0a在安装驱动程序时,Windows一般要把.inf文件拷贝一份到“Win-dows\Inf”或“Windows\Inf\Other”目录下,以备将来使用。Inf目录下除了有.inf文件外,还有两个特殊文件Drvdata.bin和Drvidx.bin,以及一些.pnf文件,它们都是Windows为了加快处理速度而自动生成的二进制文件。Drvdata.bin和Drvidx.bin记录了.inf文件描述的所有硬件设备,也许朋友们会有印象:当我们在安装某些设备时,经常会看到一个“创建驱动程序信息库”的窗口,此时Windows便正在生成这两个二进制文件。 x0dx0aWindows 9x专门提供有“添加新硬件向导”(以下简称硬件向导)来帮助使用者安装硬件驱动程序,使用者的工作就是在必要时告诉硬件向导在哪儿可以找到与硬件型号相匹配的.inf文件,剩下的绝大部分安装工作都将由硬件安装向导自己完成。
⑽ 为什么linux 需要把驱动编译到内核
因为linux的驱动是内核的一部分,内核启动时会检测硬件需要按需加载相应的驱动,如果在编译内核时没有为你的选择的硬件编译相应的模块,内核是无法加载相应的驱动的,这时候就需要你自己动手编译驱动模块了。