很多粉絲在群里提問,如何把一個模塊文件編譯到內核中或者獨立變異成ko文件。本文給大家詳解講解。
1. 內核目錄
Linux內核源代碼非常龐大,隨着版本的發展不斷增加。它使用目錄樹結構,並且使用Makefile組織配置、編譯。
初次接觸Linux內核,好仔細閱讀頂層目錄的readme文件,它是Linux內核的概述和編譯命令說明。readme的說明側重於X86等通用的平台,對於某些特殊的體系結構,可能有些特殊的說明。
頂層目錄的Makefile是整個內核配置編譯的核心文件,負責組織目錄樹中子目錄的編譯管理,還可以設置體系結構和版本號等。
內核源碼的頂層有許多子目錄,分別組織存放各種內核子系統或者文件。
具體的目錄說明如下表所示。
目錄 | 內容 |
---|---|
arch/ | 體系結構相關的代碼,如arch/i386、arch/arm、arch/ppc |
crypto | 常用加密和散列算法(如AES、SHA等),以及一些壓縮和CRC校驗算法 |
drivers/ | 各種設備驅動程序,如drivers/char、drivers/block…… |
documentation/ | 內核文檔 |
fs/ | 文件系統,如fs/ext3、fs/jffs2…… |
include/ | 內核頭文件:include/asm是體系結構相關的頭文件,它是include/asm-arm、include/asm-i386等目錄的鏈接;include/linux是Linux內核基本的頭文件 |
init/ | Linux初始化,如main.c |
ipc/ | 進程間通信的代碼 |
kernel/ | Linux內核核心代碼(這部分比較小) |
lib/ | 各種庫子程序,如zlib、crc32 |
mm/ | 內存管理代碼 |
net/ | 網絡支持代碼,主要是網絡協議 |
sound | 聲音驅動的支持 |
scripts/ | 內部或者外部使用的腳本 |
usr/ | 用戶的代碼 |
2. 編譯工具
-
make mrproper: 清除內核生成的配置文件與目標文件等,一般在第一次編譯時使用
-
導入默認配置信息(在內核根目錄中)
a) make xxx_deconfig
b) cp arch/arm/configs/xx_deconfig .config
生成默認配置文件
- 配置命令
make xxxxconfig 修改配置文件
make xconfig (圖形界面 qt庫)
make menuconfig (常用 libncurses庫)
sudo apt-get install libncurses5-dev
make config (精簡)
- 編譯內核
make uImage ---生成內核鏡像 /arch/arm/boot/uImage
- 編譯設備樹
make dtbs ---生成設備樹文件 /arch/arm/boot/dtb/xxxxxx.dtb
- 編譯生成模塊文件
make modules ---把配置值選成M的代碼編譯生成模塊文件。(.ko) 放在對應的源碼目錄下。
3. 內核編譯
現在很多基於Linux的產品開發,通常廠家都會提供集成開發環境SDK。builroot使我們搭建環境變得更加方便,但是作為初學者我們還是要掌握如何獨立編譯內核源碼。
0) 前提條件
必須先安裝交叉編譯工具鏈,關於交叉編譯工具鏈的安裝可以參考
《linux環境搭建-ubuntu16.04安裝》
在這里我們使用的是arm-none-linux-gnueabi-gcc。
1)下載內核源碼
下載地址:
https://mirrors.edge.kernel.org/pub/linux/kernel/
我們下載Linux-3.14內核(可以是更高的版本)至/home/peng目錄。
或者直接點擊下面鏈接
https://mirrors.edge.kernel.org/pub/linux/kernel/v3.x/linux-3.14.10.tar.xz
解開壓縮包,並進入內核源碼目錄,具體過程如下:
$ tar xvf linux-3.14.tar.xz
$ cd linux-3.14
2)修改內核目錄樹根下的Makefile,指明交叉編譯器:
$ vim Makefile
找到ARCH和CROSS_COMPILE,
修改:
ARCH ?= $(SUBARCH)
CROSS_COMPILE ?= $(CONFIG_CROSS_COMPILE:"%"=%)
為
ARCH ?= arm
CROSS_COMPILE ?= arm-none-linux-gnueabi-
4)配置內核產生.config文件:
導入默認配置
$ make exynos_defconfig
這里我們假定要編譯的內核最終在三星的板子上運行,soc名字是exynos,三星公司其實已經將自己的配置文件放置在
./arch/arm/configs/exynos_defconfig
執行這個命令,最終會在內核根目錄下生成.config文件,
我們編譯內核就完全依賴這個文件。
該文件是exynos開發板所需要的一些內核模塊宏定義和參數設置,這些值是廠商給的一個初始配置。
實際項目開發中,需要在這個配置文件基礎之上再重新移植自己需要的對應的驅動模塊。
5)配置內核模塊
輸入內核配置命令,進行內核選項的選擇,命令如下:
$ make menuconfig
命令執行成功以后,會看到如下圖所示的界面。其實我們在圖1.5中看到過同樣功能的界面,那個圖也是內核選項配置界面,只不過那個界面在X-window下才能執行。
其中:
- 子菜單--->
表示有子菜單,按下回車可以進入子菜單。
- 中括號[]
在每一個選項前都有個括號,有的是中括號,有的是尖括號,還有的是圓括號。
[] 表示該選項只有兩種選項,中括號中要么是空,要么是“*”;
用空格鍵可以做出選擇。
- 尖括號<>
<>選擇相應的配置時,有3種選擇,它們代表的含義分別如下。
● *:將該功能編譯進內核。
● 空:不將該功能編譯進內核。
● M:將該功能編譯成可以在需要時動態插入到內核中的模塊。
- 模塊配置圓括號()
而圓括號的內容是要你在所提供的幾個選項中選擇一項。
如果使用的是make xconfig,使用鼠標就可以選擇對應的選項。如果使用的是make menuconfig,則需要使用回車鍵進行選取。
在編譯內核的過程中,麻煩的事情就是配置這步工作了。初次接觸Linux內核的開發者往往弄不清楚該如何選取這些選項。
實際上,在配置時,大部分選項可以使用其默認值,只有小部分需要根據用戶不同的需要選擇。
選擇的原則是將與內核其他部分關系較遠且不經常使用的部分功能代碼編譯成為可加載模塊,這有利於減小內核的長度,減少內核消耗的內存,簡化該功能相應的環境改變時對內核的影響;不需要的功能就不要選;與內核關系緊密而且經常使用的部分功能代碼直接編譯到內核中。
6)編譯內核:
root@ubuntu:/home/peng/linux-3.14# make uImage
如果按照默認的配置,沒有改動的話,編譯后系統會在arch/arm/boot目錄下生成一個uImage文件,這個文件就是剛剛生成的。
7)下載Linux內核
因為不同的板子對應的uboot版本都不一樣,所以下載程序的uboot命令也會有所差異,關於驗證,本文暫不討論。
4. 獨立驅動程序的編譯
1. 編譯成獨立模塊
假定我們有以下驅動程序,要編譯成可以加載到開發板的獨立ko文件
hello.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
//#include <io/uaccess.h>
#include <linux/device.h>
#include <asm/io.h>
#include <asm/uaccess.h>
static int major = 237;
static int minor = 0;
static dev_t devno;
struct device *class_dev = NULL;
struct class *cls;
static int hello_open (struct inode *inode, struct file *filep)
{
printk("hello_open()\n");
return 0;
}
static int hello_release (struct inode *inode, struct file *filep)
{
printk("hello_release()\n");
return 0;
}
#define KMAX_LEN 32
char kbuf[KMAX_LEN+1] = "kernel";
//read(fd,buff,40);
static ssize_t hello_read (struct file *filep, char __user *buf, size_t size, loff_t *pos)
{
int error;
if(size > strlen(kbuf))
{
size = strlen(kbuf);
}
if(copy_to_user(buf,kbuf, size))
{
error = -EFAULT;
return error;
}
return size;
}
//write(fd,buff,40);
static ssize_t hello_write (struct file *filep, const char __user *buf, size_t size, loff_t *pos)
{
int error;
if(size > KMAX_LEN)
{
size = KMAX_LEN;
}
memset(kbuf,0,sizeof(kbuf));
if(copy_from_user(kbuf, buf, size))
{
error = -EFAULT;
return error;
}
printk("%s\n",kbuf);
return size;
}
static struct file_operations hello_ops =
{
.open = hello_open,
.release = hello_release,
.read = hello_read,
.write = hello_write,
};
static int hello_init(void)
{
int result;
printk("hello_init \n");
result = register_chrdev( major, "hello", &hello_ops);
if(result < 0)
{
printk("register_chrdev fail \n");
return result;
}
cls = class_create(THIS_MODULE, "hellocls");
if (IS_ERR(cls)) {
printk(KERN_ERR "class_create() failed for cls\n");
result = PTR_ERR(cls);
goto out_err_1;
}
devno = MKDEV(major, minor);
class_dev = device_create(cls, NULL, devno, NULL, "hellodev");
if (IS_ERR(class_dev)) {
result = PTR_ERR(class_dev);
goto out_err_2;
}
return 0;
out_err_2:
class_destroy(cls);
out_err_1:
unregister_chrdev(major,"hello");
return result;
}
static void hello_exit(void)
{
printk("hello_exit \n");
device_destroy(cls, devno);
class_destroy(cls);
unregister_chrdev(major,"hello");
return;
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
//proc/devices
注意我們需要編寫Makefile如下:
ifneq ($(KERNELRELEASE),)
obj-m:=hello.o
else
KDIR :=/home/peng/linux-3.14
PWD :=$(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
clean:
rm -f *.ko *.o *.mod.o *.symvers *.cmd *.mod.c *.order
endif
關於Makefile的詳解,大家可以參考我們之前的文章
《手把手教Linux驅動1-模塊化編程》
其中內核路徑:
KDIR :=/home/peng/linux-3.14
必須是我們剛才編譯過的內核源碼根目錄。
編譯時,程序可以放到其他目錄下:
用file命令查看文件屬性,是基於ARM的。該模塊文件就是與前面編譯的內核配套的驅動模塊,如果開發板的內核版本與上面編譯的版本號一致,那么該模塊文件就可以在開發板上insmod。
2. 編譯到內核
步驟:
- 1)拷貝文件
如果要將剛才的驅動程序直接編譯到內核,那么我們必須把hello.c拷貝到內核的某個目錄下。
字符設備可以考慮放到以下目錄:
linux-3.14/drivers/char
- 2)修改Makefile
root@ubuntu:/home/peng/linux-3.14/drivers/char# vim Makefile
修改如下:
該行內容是根據宏CONFIG_HELLO來決定是否編譯hello.c這個文件。
- 3)修改Kconfig
7 HELLO 取前面步驟CONFIG_HELLO下划線后面的字符串
8 tristate 表示該模塊最終有3個選項 空 * M
9 表示該模塊依賴的模塊,如果ARCH_EXYNOS4模塊沒有被選中,那么HELLO模塊也不會被編譯到內核
10 幫助信息
- 4) 重新配置
執行
make menuconfig
進入配置頁面,
輸入 / 可以根據關鍵字查找模塊所在位置。
我們添加的模塊文件的位置:
根據路徑
-> Device Drivers
-> Character devices
找到我們剛才的模塊配置路徑
此處是尖括號,因為我們設置的屬性是tristate
移動到Help處,可以看到前面我們填充的幫助信息
我們可以按下空格鍵設置為*,編譯到內核中。
選擇Save,
然后再點擊2次Exit,就可以退出。
- 5)重新編譯內核
root@ubuntu:/home/peng/linux-3.14# make uImage
這樣,我們的模塊編譯到了新生成的內核模塊文件中。
3. 補充
前面一節其實最終目的是生成CONFIG_HELLO=y 這個定義信息,並把該信息保存到內核根目錄的.config文件中。
其實我們如果不修改Kconfig,直接在.config中增加這個宏定義也是可以的。
今天內容就到這里,還等什么?抓緊操練起來吧。
文中用到的虛擬機,叫交叉編譯工具,還有源代碼,可以關注公眾號,后台回復 ubuntu,即可獲得。