有时候在学习过程中,为了方便学习一些知识,经常需要事先写一个简单的驱动 driver,然后再写一个应用程序进行调试测试,那么问题来了,怎样先写一个驱动并且编译好?下面将给出一个案列,案列程序直接引用别人的(结尾会给出原文程序链接),当然也可以自己写 。案列程序主要做的事情就是 用户空间和驱动程序的内存映射 , 直接看代码就清楚了。
zj@zj-virtual-machine:~/c_study/mmap_prj$ cat driver_demo1.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define DEVICE_NAME "mymap"
static unsigned char array[10]={0,1,2,3,4,5,6,7,8,9};
static unsigned char *buffer;
static int my_open(struct inode *inode, struct file *file)
{
return 0;
}
static int my_map(struct file *filp, struct vm_area_struct *vma)
{
unsigned long page;
unsigned char i;
unsigned long start = (unsigned long)vma->vm_start;
//unsigned long end = (unsigned long)vma->vm_end;
unsigned long size = (unsigned long)(vma->vm_end - vma->vm_start);
//得到物理地址
page = virt_to_phys(buffer);
//将用户空间的一个vma虚拟内存区映射到以page开始的一段连续物理页面上
if(remap_pfn_range(vma,start,page>>PAGE_SHIFT,size,PAGE_SHARED))//第三个参数是页帧号,由物理地址右移PAGE_SHIFT得到
return -1;
//往该内存写10字节数据
for(i=0;i<10;i++)
buffer[i] = array[i];
return 0;
}
static struct file_operations dev_fops = {
.owner = THIS_MODULE,
.open = my_open,
.mmap = my_map,
};
static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &dev_fops,
};
static int __init dev_init(void)
{
int ret;
//注册混杂设备
ret = misc_register(&misc);
//内存分配
buffer = (unsigned char *)kmalloc(PAGE_SIZE,GFP_KERNEL);
//将该段内存设置为保留
SetPageReserved(virt_to_page(buffer));
return ret;
}
static void __exit dev_exit(void)
{
//注销设备
misc_deregister(&misc);
//清除保留
ClearPageReserved(virt_to_page(buffer));
//释放内存
kfree(buffer);
}
module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("LKN@SCUT");
zj@zj-virtual-machine:~/c_study/mmap_prj$ cat mmap_demo1.c
#include
#include
#include
#include
#include
#include
#include
#include
#define PAGE_SIZE 4096
int main(int argc , char *argv[])
{
int fd;
int i;
unsigned char *p_map;
//打开设备
fd = open("/dev/mymap",O_RDWR);
if(fd < 0)
{
printf("open fail\n");
exit(1);
}
//内存映射
p_map = (unsigned char *)mmap(0, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED,fd, 0);
if(p_map == MAP_FAILED)
{
printf("mmap fail\n");
goto here;
}
//打印映射后的内存中的前10个字节内容
for(i=0;i<10;i++)
printf("%d\n",p_map[i]);
here:
munmap(p_map, PAGE_SIZE);
return 0;
}
涉及到 makefile 的知识本身很多,这里只给出最简单的编译方式,具体见如下代码
zj@zj-virtual-machine:~/c_study/mmap_prj$ cat Makefile
obj-m:=driver_demo1.o
KDIR:=/lib/modules/$(shell uname -r)/build
PWD:=$(shell pwd)
default:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions *.order *symvers *Module.markers
对于这个 makefile 文件进行简单的解释一下 :
• obj-m := <模块名>.o
obj-m 是 kbuild makefile 中用来做目标定义的,obj-m 是指编译成模块,亦即生成 ko 文件。
obj-m:=driver_demo1.o —-> 这里面 driver_deno1.o 是需要变化的,通常而言和源代码的文件同名即可,比如 c 代码是 driver_demo1.c,所以这里就是 driver_demo1.o,编出来就是 driver_demo1.ko。
• <模块名>-objs := <目标文件>
但是有时候,ko 是由 kxxx.o file1.o file2.o这样多个.o文件编译成 driver_demo1.ko模块,那需要:
obj-m := driver_demo1.o
• KDIR
KDIR:=/lib/modules/$(shell uname -r)/build
这是我们正在运行的操作系统内核编译目录。也就是编译模块需要的环境。
uname -r可以查看当前系统使用的内核版本以及子版本号。
• M=
指定源文件的位置
把驱动编译好了以后,我们需要简单 check 一下,check 之前要先装载编译好的模块,最后还要记得卸载我们的模块 。因为驱动程序会在设备去创建设备,所以这个也可以检查 。
zj@zj-virtual-machine:~/c_study/mmap_prj$ sudo insmod driver_demo1.ko
zj@zj-virtual-machine:~/c_study/mmap_prj$ ls -la /dev | grep mym*
1 root root 10, 53 12月 28 09:08 mymap # create the device
zj@zj-virtual-machine:/sys/module$ ls -la | grep driver_demo1
drwxr-xr-x 5 root root 0 12月 28 09:08 driver_demo1 # insmod the module
zj@zj-virtual-machine:/sys/module/driver_demo1/sections$ sudo cat .bss
0xffffffffc06fb4c0 # it can help us to debug the driver in kernel space
最后 run 一下应用程序 , 结果如下 , 表明这个过程还是比较成功的 。
zj@zj-virtual-machine:~/c_study/mmap_prj$ sudo ./mmap_demo1
[sudo] password for zj:
Sorry, try again.
[sudo] password for zj:
0
1
2
3
4
5
6
7
8
9
分析案例中程序的来源
http://blog.csdn.net/dlutbrucezhang/article/details/9967849