編寫過設備驅動就會經常碰到module_init這個宏來定義驅動入口函數。這個宏定義了一個函數指針指向我們的驅動入口函數,等到上電的時候就將這些一個個的函數指針拿出來調用,那么各個驅動得到加載。特別的是:這些函數指針是存放在linux kernel本體的某個段里。這是通過gnu 的__attribute__來修飾的。
實際上,kernel里面有非常多的段,這些段的起始地址和結束地址都能被源碼里得到,因此學會看鏈接腳本和引用里面的段再或者是自定義段對于理解kernel的源碼大有益處。
鏈接腳本
任何一個可執行程序是被鏈接腳本將一個個的.o鏈接起來的。kernel也不例外,kernel的每個架構都有一個默認的鏈接腳本路徑,如:arch/arm/kernel/vmlinux.lds
.init.arch.info : {
__arch_info_begin = .;
*(.arch.info.init)//機器信息段
__arch_info_end = .;
}
部分段的形式如上,__arch_info_begin 和__arch_info_end 就能被源碼引用,得到這個地址范圍的數據。怎么把數據放入這些段:使用attribute修飾你想要放入這個段的數據結構 。
對于kernel的段組成有非常多,按需查看源碼即可。
實戰
目的:
了解自定義數據結構通過鏈接腳本如何放入kernel鏡像
- 了解自定義數據結構通過鏈接腳本如何放入kernel鏡像
- 了解如果在合適時候使用這些數據結構
在這我將定義一個數據結構,放入自定義的段里面,然后在驅動加載的時候,取出這個數據結構里面的數據,打印出來。
1.在vmlinux.lds增加自定義段
.my_section :{
my_section_begin = .;
*(.my_section)
my_section_end = .;
}
在vmlinux.lds中間找個位置,定義一個.my_section段,并且使用my_section_begin 和my_section_end 記錄這個段的起始地址和結束地址。
2.定義一個宏修飾數據結構放在.my_section
#define my_section __attribute__((__section__(".my_section")))
my_section 給這個修飾符取一個常用明顯的名字,常見于kernel的用法. attribute (( section ("xxx")))的語法可以參考gnu相關文檔。
意思是使用my_section 修飾的數據結構都會被放到.my_section這個段里面
3.聲明段起始地址和結束地址
extern const struct person my_section_begin[],my_section_end[];
extern表示my_section_begin,my_section_end已經在鏈接腳本里面定義了,使用一個同名數組名表示這個地址,這個段里面存放的是一個個的自定義數據結構struct person。
4.修飾自定義數據結構
struct person{
int age;
char *name;
};
struct person my_section lzy ={
18,
"liangzhengyi",
};
經過my_section修飾,lzy 這個實例會被放到vmlinux.lds定義的段里面。
5.完整代碼
#include < linux/kernel.h >
#include < linux/module.h >
#include < uapi/linux/sched.h >
#include < linux/init_task.h >
#include < linux/init.h >
#include < linux/fdtable.h >
#include < linux/fs_struct.h >
#include < linux/mm_types.h >
#include < linux/list.h >
#include < linux/types.h >
#define my_section __attribute__((__section__(".my_section")))
struct person{
int age;
char *name;
};
struct person my_section lzy ={
18,
"liangzhengyi",
};
extern const struct person my_section_begin[],my_section_end[];
static int __init section_add_init(void)
{
struct person *addr_begin = my_section_begin;
struct person *addr_end = my_section_end;
printk("section_add_init\\n");
printk("find section %d %s",addr_begin- >age,addr_begin- >name);
printk("my section lenth:%d\\n",(my_section_end-my_section_begin)*sizeof(struct person));
return 0;
}
//內核模塊退出函數
static void __exit section_add_exit(void)
{
printk("section_add_exit\\n");
}
module_init(section_add_init);//入口
module_exit(section_add_exit);//出口
MODULE_LICENSE("GPL");//許可證