linux分为内核态和用户态,他两不能直接访问,必须通过”中间商”联系,关系如下:
用户空间:应用程序
|
|系统调用(陷入)
|
内核:linux驱动
Linux驱动的重点就是驱动框架,驱动运行方式有两种:
①:将驱动编译进linux内核中,代码release或者驱动模块需要;
②:将驱动编译成模块(.ko文件),再人为insmod/modprobe加载驱动模块,一般在调试阶段。
接下来就讲讲字符设备驱动入门流程和注意事项。
目录
1. 加载与卸载驱动模块
注册函数模板:
static int __init xxx_init(void) //执行insmod的时候调用
static void __exit xxx_exit(void) //执行rmmod的时候调用
module_init(xxx_init); //起声明作用
module_exit(xxx_exit); //起声明作用
使用modprobe,因为modprobe更智能,能关联加载驱动所需的其他驱动模块(但是在简单驱动开发验证还是觉得insmod方便简单)
2. 字符设备的注册和注销
register_chrdev(major(主设备号), name(设备名称), fops(fileoperations类型指针))
unregister_chrdev(major(主设备号), name(设备名称))
在上面的init和exit两个函数中分别调用这两个函数,还有需要定义一个fileoperations结构体。
3. 设备驱动框架
Staitc chrtest_open 打开设备
Staitc chrtest_read 从设备读取
Staitc chrtest_write 向设备写入
Staitc chrtest_release 关闭、释放设备
/*定义一个file operations结构体*/
Static struct file_operations test_fops={
.owner = THIS_MODULE,
.open = chrtest_open,
.read = chrtest_read,
.write = chrtest_write,
.release = chrtest_release,
};
/*驱动入口函数*/
Staitc int __init xxx_init(void)
{
Int ret = 0;
ret = register_chrdev(200,”chrtest”,&test_fops);
If(ret < 0)
{
Return -1;/*注册失败*/
}
}
/*驱动出口函数*/
Static void __exit xxx_exit(void)
{
unregister_chrdev(200, “chrdev”);
}
module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE(“GPL”);
4. 编写测试APP
以操作文件的形式open read write close,因为linux皆文件。
int(fd) open(const char *pathname, int flags)
Pathname:文件路径、设备
flags:O_RDONLY只读/WRONLY只写/RDWR读写......
ssize read(int fd, void * buf, size_t count)
ssize write(int fd,void *buf, size_t count)
int close(fd)
5. 编译
驱动模块的编译:编写一个Makefile,执行make 生成.ko模块。
测试app的编译:arm-linux-gnueabihf-gcc xxx.c -o xxx 因为编译出来的执行文件是要在arm内核下运行,所以使用交叉编译器。
6. 运行测试
①:加载模块:insmod xxx.ko/modprobe xxx.ko(执行不成功,要先执行depmod)
②:创建设备节点:在/dev目录下创建与之对应的设备节点文件,mknod /dev/xxx c 200 0
mknod /dev/xxx c(字符设备) 200(主设备号) 0(次设备号)
③:设备验证:./xxx(APP) /dev/xxx 1(参数)
④:卸载:rmmod xxx.ko
7. 其他
①.设备都有一个设备号(32位),分别由主设备号(12位 0-4095)和次设备号(20位)组成。主设备号是具体的驱动,像鼠标驱动;次设备号是主驱动的具体应用个数,像雷蛇、双飞燕啊这些鼠标插电脑会有几个鼠标设备一样,但是驱动的名字不一样。
②.静态分配设备号:查看当前系统所使用的设备号:cat /proc/devices,在确定未使用的设备号作为改驱动设备号。
动态分配设备号:int alloc_chrdev_region(dev_t *dev(设备号),unsigned baseminor(次设备号起始),unsigned count(申请设备个数), const char *name(设备名称))
注销设备:void unregister_chrdev_region(dev_t from(需要释放设备号), unsigned count(从from需要释放个数))
③.Linux内核没有printf,只有printk。因为printf是运行在用户态中(应该是为了做区别吧)。
printk可以根据宏定义,决定需要打印的信息。因为内核空间和用户空间不能直接操作,所以就会有从内核<==>用户的接口函数:copy_to_user(内核->用户read)和copy_from_user(用户write->内核),这两个函数都是站在用户态理解和调用。
参考:【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.4.pdf