linux分为用户态和内核态,内核态拥有更高的权限(例如可以关闭中断实现更高的优先级),这周在内核态开发了一个简单的驱动,做个经验总结。
任务背景:
由于业务需求433Mhz通讯,而采用的芯片为纯透传模式(无fifo寄存器),所以产生了较高的序要求,用户态无法
完成该工作,故在内核态开发(最高优先级,关闭所有中断,相当于百分百占用cpu)。
对于内核态与用户态最直观浅显的理解是:用户态运行程序的方式为:
编写.c文件→编译器用特定编译选项(适配当前系统)得到可执行文件→开整
内核态运行特定程序的方式为:
编写.c文件→在该系统的sdk环境中编译出.ko文件→通过insmod插入该驱动→开整
当然 这只针对我这一个案例(一个内核态驱动开发案例)
1.环境搭建与makefile编写
项目面临的第一个问题是如何写出一个ko驱动,在拥有整个系统sdk固件的情况下,我只需要在根目录 source build/envsetup.sh,并编写特定的makefile文件便可编译出ko驱动文件,我编写的makefile文件如下(一开始就是照猫画虎,不过目前能明白各个变量的意义了)
最主要的几行规则为
相当于告诉系统,编译此ko的内核环境目录。以及编译的目标obj-m(若要将多个.c文件编译为ko文件则需要用其他方法,网上能找到相关资料)
2.代码编写
代码反而是相对不那么复杂的工作,在拟定好具体通讯协议之后,本项目用到的只是调用pwm与操作io口罢了。不过这里有几点值得讲的是:
1.前文提到了协议对时序要求较高,故在每个字节传输的过程中不允许被打断,此处在内核态关闭了整个系统中断。利用的函数为local_irq_disable()与local_irq_enable()
2.在这个过程中使用了在内核态中创建线程以及设置线程优先级的知识,大致总结以及具体源码如下:
1内核态线程不同于用户态,没有子线程的概念,完全是一个平行的线程。
2内核态线程设定优先级使用sched_setscheduler函数。
源码:
static struct task_struct *ptest_task;
static struct sched_param para;
ptest_task = kthread_create(test_thread_fnc, NULL, “test_thread”);
if(IS_ERR(ptest_task))
{
printk("Unable to start kernel thread. ");
err = PTR_ERR(ptest_task);
ptest_task = NULL;
return err;
}
para.sched_priority = 1;
sched_setscheduler(ptest_task, SCHED_FIFO, ¶);
wake_up_process(ptest_task);
至于pwm操作则是调用了对应的驱动,没什么好讲的。
3.对驱动的相关理解
由于驱动代码最终需要被用户调用,对用户态来说调用方式为:
1 linux insmod xxx.ko文件(对应驱动中的init函数会被调用)
2 用户态代码 open /dev/xxx(驱动设备名) 此时驱动会以一个io文件的形式供用户调用
3 采用write read ioctl 等方法操作驱动 (会调用驱动中写好的对应函数)
例如在该该截图中,驱动将各个io操作映射到了对应的函数,供用户操作。
此处也让我对linux的“万物皆文件”有了新的认识。
3.具体运行
具体运行方式就很简单了,编译出对应的.ko文件后,直接用insmod方法便可插入驱动,(驱动在linux系统中会被作为一个设备挂载在/dev目录中),而用户open该设备后,便可用write,open,read,ioctl等接口操作对应函数,实现对应功能了。
(例如用两行代码展示用户态open对应设备后,调用 write函数,驱动将对应的信息编码为io口的高低电平波形输出)
fd = open(/dev/send433);
write(fd, “1234”)