ubuntu 10.04驱动程序的编译与加载
第1步:查看自己OS是否已安装好内核源码包
使用命令查看自己系统的源码包型号
# ls /usr/src
如果存在类似的目录,说明已安装。
第2步:查看自己OS是否已安装libncurses5-dev工具包
如果没有安装使用命令:
# apt-get install build-essential
libncurses5-dev是库函数工具,编译内核必须使用。
第3步:编辑驱动程序char.c
这是书中P129程序,书中有不足之处,要改动。下面的程序已编译通过。
执行命令: $ vi char.c
程序内容如下:
#include
#include
#include
#include
#include
#include
#include
#include
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Bob
Geng");
MODULE_DESCRIPTION("a simple driver");
#define N
128
int major = 250;
int minor = 0;
struct cdev mycdev; //字符型结构体
char buf[N] ={"hello world !!"};
int char_open (struct inode * myinode, struct file *fp)
{
printk("char
is opened\n");
return
0;
}
int char_release (struct inode *myinode, struct file
*fp)
{
printk("char
is closeed\n");
return
0;
}
static ssize_t char_read (struct file *filep, char __user
*user_buf, size_t count, loff_t * off)
{//1.
ssize_t
:ssize_t是signed
size_t;size_t:
unsigned int
//2. Off:当前文件的偏移量
ssize_t
ret =0;
long
num =0;
printk("char_read
is called\n");
printk("count
is %d\n",count);
num
= copy_to_user(user_buf,buf,count);
if(num
< 0 )
{
printk("copy_to_user
is failed\n");
return
ret;
}
return
ret;
}
ssize_t char_write (struct file *filep, const char __user *from,
size_t count, loff_t *off)
{
ssize_t
ret =0;
long
num =0;
printk("char_write
is called \n");
printk("count
is %d\n",count);
// if(count
> N ) return -ENOMEM;
if(count
> N ) count = N ;
num
= copy_from_user(buf,from,count);
if(num
< 0 )
{
printk("copy_to_user
is failed\n");
return
ret;
}
printk("from
user is %s\n",buf);
return
ret;
}
struct file_operations fops={
.owner
= THIS_MODULE,
.open
= char_open,
.release
= char_release,
.read
= char_read,
.write
= char_write,
};
static int __initchar_init(void)
{
int
ret;
dev_t
devno = MKDEV(major,minor);
ret
= register_chrdev_region(devno,1,"char");//静态申请设备号
if(ret
< 0 )
{
printk("fail
to get devno\n");
return
ret;
}
mycdev.owner
= THIS_MODULE;
cdev_init(&mycdev,&fops);
ret
= cdev_add(&mycdev,devno,1);
if(ret
< 0 )
{
printk("cdev_add
fail to system\n");
return
ret;
}
printk("init_module\n");
return
0;
}
static void __exit char_exit(void)
{
dev_t
devno = MKDEV(major,minor);
cdev_del(&mycdev);
unregister_chrdev_region(devno,
1);
printk("cleanup_module\n");
}
module_init(char_init);
module_exit(char_exit);
测试程序test.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define N
128
char buf[N] ;
int main()
{
int
fd;
if
( (fd = open("/dev/mychar",O_RDWR)) <
0)
{
perror("open");
exit(-1);
}
if(read(fd,buf,N
) < 0 )
{
perror("read");
exit(-1);
}
printf("read
from mychar is %s\n",buf);
//
memset(buf,0,sizeof(buf));
//strcpy(buf,"goddbye\0");
printf("please input second buf:\n");
scanf("%s",buf);
if(write(fd,buf,N+1)
< 0)
{
perror("write");
exit(-1);
}
if(read(fd,buf,N
) < 0 )
{
perror("read");
exit(-1);
}
printf("second read from mychar is
%s\n",buf);
getchar();
printf("mychar
is opened\n");
close(fd);
}
第4步:编译驱动程序char.c成char.o
说明:
①2.6版本以前的内核编译采用GCC工具,也就是书中P130的方式。
②2.6版本以后的内核编译都是如下方式。
③驱动程序编译一般采用make
工具进行,所以必须要会写makefile文件。
④经实验证明,驱动程序的Makefile文件名(m必须是大写M)否则会出错。
char.c对应的驱动程序Makefile文件内容如下:(可通用)
华清远见嵌入式培训编写的Makefile内容如下显示:(测试通过,可通用)
ifeq ($(KERNELRELEASE),)
KERNELDIR ?= /lib/modules/$(shell uname
-r)/build
#KERNELDIR ?= /home/linux/workdir/source-pack/linux-3.2-net/
(交叉编译)
M=$(PWD) modules
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
Module* modules*
.PHONY: modules modules_install clean
else
obj-m :=char.o
endif
注解:
①ifneq
...else
...endif:选择结构。ifneq和ifeq分别是一个自定函数(某lib中的),根据名字来推断应该是一个是否equal(相等)的判断,第一个值$("xx")应该是获取名为xx的对象引用,至于第二个参数,应该会有默认值的,或者允许空值(NULL)。
②Ifeq...else...endif
选择结构。
③在当前目录下输入make
后,会执行当前目录下的Makefile,执行分三步执行:
a)Makefile:1:
KERNELRELEASE=
//KERNELRELEASE是在内核源码的顶层Makefile中定义的一个变量,在第一次读取执行此Makefile时,KERNELRELEASE没有被定义。
b)KERNELDIR
?= /lib/modules/$(shell uname -r)/build (本地开发)
等于
c)/lib/modules/3.2.0-29-generic-pae/build
这个目录存放的是编译内核必须的一些头文件和库
d)#KERNELDIR
?= /home/linux/workdir/source-pack/linux-3.2-net/
(交叉开发)
e)PWD
获取用户的当前目录。
f)在输入make后默认会执行第一个目标,第一目标:modules:
g)进而执行:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
h)$(MAKE)
= make
i)-C
$(KERNELDIR) : 指明跳转到内核源码目录下读取那里的Makefile
j)
本题具体分析:进入子目录(/usr/src/linux-headers-3.5.0-23-generic)
下,去执行子目录下的Makefile
k)M=$(PWD):
表明然后返回到当前目录继续读入、执行当前的Makefile
l)
本题具体分析:/home/linux/linux-driver/01-day/02-char-module_init(具体分析)
m)modules
: 告诉内核我要编译模块
n)Makefile:2:
KERNELRELEASE==3.5.0-23-generic
o)第二次:
执行子目录下的Makefile后,会再次调用当前目录下的Makefile在执行子目录(/usr/src/linux-headers-3.5.0-23-generic)下的Makefile后会对KERNELRELEASE
进行赋值KERNELRELEASE=3.5.0-23-generic
p)ifeq
($(KERNELRELEASE),)条件为假
q)执行else分支
r)obj-m
:= char.o(obj-m
表示的要编译成模块所依赖的*.o文件)(动态编译)
s)obj-y
:静态编译
t)执行后会把char.c编译程char.o文件
u)Makefile:3:
第3次 :
会把char.o
生成char.ko文件
第5步:执行make命令,开始编译
执行命令:# make
(以root用户执行)
本程序的编译结果如下显示:
说明:
①.ko
是Linux 2.6内核使用的动态编译的后缀名,也就是模块文件,用来在Linux系统启动时加载内核模块。
②.o
是目标文件,相当于windows中的.obj文件。
第6步:驱动程序的加载
使用命令:
# insmod char.ko (针对此程序,加载.ko文件,而不是.o文件)
查看是否成功加载命令:# lsmod
第7步:设备文件的创建
使用命令: 针对此程序。
#mknod /dev/mychar c 250 0
在/dev目录下创建设备文件。
1.命令中的数字要和驱动程序定义的major,minor保持一致。
2.Mychar文件名与测试程序
中的名字一致。
第8步:test.c生成可执行程序
如果在x86平台下测试,则使用如下命令:
# gcc test.c -o test
如果在ARM平台下测试,则使用如下命令:
# arm-linux-gcc test.c -o test
或用其它交叉编译工具也行。(编译工具根据自己安装的自定)
第9步:执行测试程序,验证驱动程序
针对此程序,执行如下命令:
# ./test
运行如下: