Clion for linux driver developer

为linux device driver developer准备的Clion配置指南。

Clion 是JetBrains公司最近推出的跨平台C++ IDE,
如果没有听过Clion,那一定听过IntelliJ Idea,如果还没听过,那一定听过Android Studio,他们
其实都是兄弟产品(Android Studio是基于IntelliJ Idea开发的)。

Clion用CMake作为其编译系统(其他的自定义Makefile也支持),利用CMake的配置文件创建编辑器本身
的代码环境,比如子项目,比如预处理宏等,这跟IntelliJ Idea利用Gradle作为编译系统是同样的道理。

废话不多说,现在开始我们的驱动开发环境配置。

代码路径问题

Clion创建CMake工程后,默认把CMakeLists.txt
所在的目录作为工程根目录,然后它就开始分析根目录下的所有C/C++代码,头文件查找路径是很重要的,
当然它会使用编译器(gcc, g++, clang, visual c等)默认的头文件路径,在linux上就是
/usr/include/usr/local/include之类的路径(除非你修改了默认路径)。但是我们的驱动代码
都是放到kernel/drivers/目录下的,不可能复制出来修改然后再复制回去,太low!聪明的你可能
已经想到解决方法了,对,就是符号链接,ln -s把kernel里你的驱动代码路径链接到项目根目录下,
完美!

还有一种方法,Clion允许修改工程根目录,完全可以直接把kernel目录作为工程根目录,但是如此
巨量的代码,你是想类似Clion吗?!即使可以把用不到的目录(绝大部分都用不到)标记为忽略
那也太繁杂了!

头文件路径设置

Clion用library的观点来看待头文件,它把头文件看做是library的导出符号集合,然后把这些library
单独组织起来构成External Libraries视图,所以问题就明确了: 一个头文件路径就看做是一个library,
我们要开发驱动,自然要把kernel里的头文件引进来(其实驱动开发时,kernel源文件不是必须的,
不过有kernel源码引进来,随时查看各个kenrel函数的实现方式还是蛮爽的),
/kernel/include是必须的,然后CPU架构相关的头文件也是必须的,如果/arch/arm/include
arch/arm64/include对应到ARM平台架构。

设置方式就是配置CMakeLists.txt文件:

1
2
3
4
5
6
7
8
include_directories(
# kernel headers
"${KERNEL_ROOT}/include"
"${KERNEL_ROOT}/arch/arm/include"
"${KERNEL_ROOT}/arch/arm64/include"
# kernel source
"${KERNEL_ROOT}/mm"
)

mm/目录引进来干嘛?!因为我想看kernel的源码的,include_directories指令不仅可以引入
头文件,还可以直接把库文件引进来哦,这样我们就可以有选择性的把kernel的代码引进来了,
避免让Clion解析整个kernel的源码。

另外还有一个不完美的地方,Clion默认把系统头文件引入进来,但是系统头文件版本跟我们用的
kernel版本可能不一致,尤其是交叉编译的时候,如我们为android开发驱动,android可能只是用
3.x的kernel,而我们的linux系统kernel可能已经是4.x,或者它还是2.x,这有时会造成困扰,
所以我们还是用kernel编译之后生成的/usr/include目录吧,该目录位于kernel_obj/usr/include
当然前提是你的kernel编译过了,这样可以保证版本统一。然后呢,我们还要把默认的系统头文件
干掉才行啊,这个很简单,因为Clion使用CMake的构建结果构造代码环境,所以我们只需配置
CMake就好了,在CMakeLists.txt里面添加编译器选项来解决这个问题:
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -nostdinc")
用c99选项是因为kernel大量使用c99的feature,而-nostdinc就是说no standard include files

符号解析问题

代码路径问题解决了,头文件引入问题解决了,接下来打开你的C代码,啊呀,WTF,一篇红啊!
这么多符号都解析不出来,Clion你难不成是徒有虚名?!
其实这不能怪Clion,你把哪个IDE弄来都是这样,Eclipse CDT,KDevelop,QTCreator,谁都白扯,
这是代码问题。问题就出在__KERNEL__这个宏身上,原来kernel为了保护其代码,故意设置了这么一个
宏,只有在编译kernel的时候才会在命令行传递这个宏进去,像libc等C runtime编译时要用到kernel
的部分代码,但是它不会设置__KERNEL__这个宏,于是被这个宏保护的kernel代码它是看不到的。
自然,我们的Clion没有定义这个宏,那些kernel代码(大部分代码)它也是看不到的,于是parser在
解析头文件时,很多符号是解析不出来的,然后只能红红火火起来了。

怎么解决呢? 没有这个宏,我们手动添加这个宏不就好了,别忘了,Clion使用CMake的构建结果来
构造代码环境,所以我们只需给CMake添加预处理宏即可,还是在CMakeLists.txt里面,
add_definitions(-D__KERNEL__=1)

Reload CMake Project之后,发现碍眼的红色提示消失了,但是别高兴的太早,你看下kmalloc
这个定义咋是空的呢?WTF!

别急,问题跟__KERNEL__宏是一样的,原来kernel在编译之前会配置一堆的config,make menuconf
就是用来生成这些config的,kernel/include/generated/autoconfig.h就是啦,这里面定义了
上千条宏定义,kernel代码根据这里的宏定义而走不同的代码,所以跟__KERNEL__一样,把这些
宏也一并加进去吧!美中不足的是,没有方法可以直接吧autoconf.h直接丢给CMake让它解析宏定义,
只能手动add_definitions,不过好在这些宏比较规律,用正则表达式来查找替换,也花不了几分钟。

Bingo,配置完成。什么代码高亮,爽歪歪的代码补全(折腾了半天还不都是为了你啊),错误提示等等,
Happy coding!

示例

最后来一个配置示例,这是我用来做Android驱动开发的配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cmake_minimum_required(VERSION 3.5)
project(drivers) # 工程名字,随你怎么叫都行

include(kernel_config.cmake) # 一堆的 add_definitions(xxx),就不细说了

set(MY_ANDROID_ROOT_DIR "xxx") # 定义一个变量,方便后面使用
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -nostdinc") # C 编译器设置

add_executable("drivers" main.c) # dummy target,没有这个的话external libraries就是空的,
# 弄一个main.c糊弄糊弄Clion

include_directories(
"${MY_ANDROID_ROOT_DIR}/kernel/include"
"${MY_ANDROID_ROOT_DIR}/kernel/arch/arm/include"
"${MY_ANDROID_ROOT_DIR}/kernel/arch/arm64/include"
"${MY_ANDROID_ROOT_DIR}/out/target/product/msm8952_64/obj/KERNEL_OBJ/usr/include"
# 下面这些随便你自己写吧,想看哪些代码就写哪些
"${MY_ANDROID_ROOT_DIR}/kernel/mm"
)

(over)