回顾day01
1.搭建环境
编译器
内核编译
tftp
nfs
2.内核模块的编译
驱动程序工作于内核态
2.1直接编译进内核
2.2编译成内核模块
1).c放到内核源码目录中修改对应的Kconfig Makefile
2).c放到自己创建的目录,完成简单的Makefile
obj-m
+= xxx.o
make -C /opt/kernel M=$(pwd) modules
3).完成Makefile
make 就可以完成
day02
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("GPL")
int __init helloworld_init(void){
printk("hello world\n");
return 0;
}
void __exit helloworld_exit(void){
printk("bye bye\n");
}
moudle_init(helloworld_init);
moudle_exit(helloworld_exit);
1.__init
->
#define __init
__section(.init.text)_cold notrac...
vmlinux.lds链接文件
vi
arch/arm/kernel/vmlinux.lds
__init 定义的函数放到了.init.text段中去
__exit定义的函数放到了.exit.text段中去
被以上两个宏修饰的代码段执行一次后释放对应的内存资源
被module修饰的函数在加载模块时会调用到
被module修饰的函数在卸载模块时会调用到
----------------------------------------------------------------------------------
1.内核导出符号
a.c
int max(int,int)
b.c
main()
{
max(...);
}
第一种方式
在b.c extern int max(int,int);
第二种方式
a.h extern int max(int,int);
b.c
#include
"a.h"
内核中导出符号 EXPORT_SYMBOL
esdexp.c
#include<linux/init.h>
#include<linux/module.h>
MODULE_LICENSE("GPL")
int esdexp_mult(int x,int y){
printk("enter esdexp_mult!\n");
return x*y;
}
int esdexp_divd(int x ,int y){
printk("enter esdexp_divd");
return x/y;
}
/*导出符号*/
EXPROT_SYMBOL(esdexp_mult);
EXPROT_SYMBOL(esdexp_divd);
esdimp.c
#include<linux/init.h>
#include<linux/module.h>
MODULE_LICENSE("GPL");
extern int esdexp_mult(int,int);
extern int esdexp_divd(int,int);
int __init esdimp_init(void){
- int result=0
printk("enter esdimp_init!\n");
//int result=0; 局部变量
result=esdexp_mult(10,17);
printk("result %d\n",result);
return 0;
}
void __exit esdimp_exit(void){
int result=0;
esdexp_divd(10,2);
printk("result %d\n",result);
}
module_init(esdimp_init);
module_exit(esdimp_exit);
@注意局部变量如此编译那么会编译不过,ISO90局部变量要放在可执行代码之前
编译后顺序安装
insmod esdexp.ko
insmod esdimp.ko
lsmod //查看
rmmod esdimp.ko
rmmod esdexp.ko
2.模块参数
内核提供了一种机制:在用户空间可以修改内核模块中全局变量的值.
#include<linux/init.h>
#include<linux/module.h>
MODULE_LICENSE("GPL");
static short mpshort=1;
static int mpint=10;
static char* mpstring="hello";
static int mparry[2]={100,200};
/*模块参数的声明*/
//module_param(name,type,perm);
//name参数名 type 变量类型名 perm 用户炒作的权限
module_param(mpshort,short,S_IRWXU);
module_param(mpint,int,S_IRWXU);
module_param(mpstring,charp,S_IRWXU);
//module_param_array(name.type,nump,perm);
//nump 如果数组在参数加载时设置,该值为加载时设置的数据个数,
不允许传递闭模块允许个数更多的值
module_param_array(mparray,int,NULL,00700);
init __init modparam_init(void){
printk("mpshort=%d\n",mpshort);
printk("mpint=%d\n",mpint);
printk("mpstring=%s\n",mpstring);
printk("mparray=%d %d\n",mparray[0],mparray[2]);
return 0;
}
void __exit modparam_exit(void){
printk("mpshort=%d\n",mpshort);
printk("mpint=%d\n",mpint);
printk("mpstring=%s\n",mpstring);
printk("mparray=%d %d\n",mparray[0],mparray[2]);
}
module_init(modparam_init);
module_exit(modparam_exit);
编译代码
insmod modparam.ko mpshort=100 mpint=200 mpstring="world" mparray=300,400
安装上后查看文件
ls /sys/module/moduleparam/parameters/ -l
echo 55>mpshort
cat mpshort
rmmod modparam.ko(注意:在用户空间可以修改内核模块中全局变量的值)
注意模块参数所支持的数据类型如下
3.内存管理
逻辑地址:汇编文件中使用的偏移地址
虚拟地址(线性地址):UC编程时使用的地址为虚拟地址
每个进程都0-4G独立空间 0-3G用户 3-4G内核地址
内核驱动开发时使用的也是虚拟地址。
物理地址:出现在地址总线上的值
段式管理单元:
16位cpu,内部地址总线是20bits,寻址能力1M,内部寄存器是16bits
段基址寄存器 CS DS SS ES
段内偏移寄存器
IP SP BX
虚拟地址=段基址<<4+段内偏移
32cpu,内部总线32bits,内部寄存器是32bits
实模式:类似于以上16位CPU的段式管理单元
保护模式:绝大多数工作时间内工作处于保护模式
虚拟地址和物理内存的关系(页式管理单元)
#linux内核中没有(有限的)使用了段式管理机制,充分使用了页式管理机制
linux始终认为基地址寄存器值为零,逻辑地址=虚拟地址
充分使用了页式管理机制:四级页表
linux内存管理的最小单元:页。通常32位CPU一页为4K
struct page
内核中内存的分配:
用户空间
malloc/free
new/delete
valloc
内核空间
kmalloc/kfree
vmalloc/vfree(申请到的物理空间可能不连续)
__get_free_pages/free_pages
内存页
#inlcude<linux/init.h>
#include<linux/module.h>
#include<linux/fs.h>
#include<linux/slab.h>
#include<linux/vmalloc.h>
MODULE_LICENSE("GPL");
unsigned char * kernelkmalloc=NULL;
unsigned char * kernelpagemem=NULL;
unsigned char * kernelvmalloc=NULL;
#define PAGE_NUM 4
init __init kernelspace_init(void){
int ret=-ENOMEM;
kernelkmalloc=(unsigned char*)kmalloc(x,GFP_KERNEL);
/*
void * kmalloc(size_t size,gfp_t flags)
参数一是空间大小,
参数二 GFP_KERNEL分配内存,分配过程中可能导致睡眠(中断上下文中是不允许睡眠的)
GFP_ATOMIC分配过程中不会导致睡眠
GFP_DMA 申请到的内存位于0~16M之间
__GFP_HIGHMEM 申请高端内存(896M以上的物理内存)
*/
if(IS_ERR(kernelkmalloc)){
printk("kmalloc failed!\n");
ret=PTR_ERR(kernelkmalloc);
goto failure_kmalloc;
}
printk("kmalloc space:0x%lx!\n",(unsigned long)kernelkmalloc);
kernelpagemem=(unsigned char*)__get_free_pages(GFP_KERNEL,PAGE_NUM);
if(IS_ERR(kernelpagemem)){
printk("get free page failed!\n");
ret = PTR_ERR(kernelpagemem);
goto failure_get_free_pages;
}
kernelvmalloc=(unsigned char*)vmalloc(1024*10);
if(IS_ERR(kernelvmalloc)){
printk("Vmalloc failed\n");
ret = PTR_ERR(kernelvmalloc);
goto failure_vmalloc;
}
return 0;
failure_vmalloc:
free_page((unsigned long)kernelpagemem,PAGE_NUM);
failure_get_free_pages:
kfree(kernelkmalloc);
failure_kmalloc:
return ret;
}
void __exit kernelspace_exit(void){
vfree(kernelvmalloc);
free_page((unsigned long)kernelpagemem,PAGE_NUM);
kfree(kernelkmalloc);
}
module_init(kernel_init);
module_exit(kernel_exit);
注意:这里需要注意内存泄漏的问题
4内核链表
struct student{
char name[20];
int age;
int sex;
...
struct student *next
}
链表的增删改查
struct employee{
char name[20];
int age;
int sex;
int id;
...
struct employ *next;
}
include/linux/list.h
struct list_head{
struct list_head *next,*prev;
}
;
#include<linux/init.h>
#include<linux/module.h>
#include<linux/slab.h> //kmalloc
#include<linux/list.h> //list
- #include<linux/error.h>
MODULE_LICENSE("GPL");
#define EMPLOYEE_NUM 10
struct employee{
char name[20];
int id;
int salary;
int age;
struct list_head list;
}
/*定义链表头节点*/
struct list_head employee_list;
struct employee *employeep=NULL;
struct list_head *pos=NULL;
struct employee *employee_temp=NULL;
int __init listtest_init(void){
int i=0;
/*初始化链表头节点*/
INIT_LIST_HEAD(&employee_list);
/*申请employee的空间*/
employeep=kmalloc(sizeof(struct employee)*EMPLOYEE_NUM,GFP_KERNEL);
if(IS_ERR(employeep)){
printk("kmalloc failed!\n");
return -ENOMEM;
}
//动态申请完一定要重新初始化
memset(kmalloc,0,sizeof((struct employee)*EMPLOYEE_NUM));
/*初始化每个struct*/
for(;i<EMPLOYEE_NUM;i++){
sprintf(employee[i].name,"employee%d",i);
/*添加节点到链表中去*/
list_add(&(employee[i].list),&employee_list));
}
/*链表节点的遍历*/
list_for_each(pos,&employee_list){
employee_temp=list_entry(pos,struct employee,list);
printk("employee name:%s\n",employee_temp->name);
}
return 0;
}
void __exit listtest_exit(void){
/*
int i=0;
for(;i<EMPLOYEE_NUM;i++)
list_del(&(employrrp[i].list));
*/
kfree(employeep);
}
module_init(listtest_init);
module_exit(listtest_exit);
INIT_LIST_HEAD
list_add
list_del
list_for_each
list_entery(?)
1)list_for_each(结束条件为什么是pos!=(head))
2)增加节点的0,1,2,3.....
遍历时9,8,7,...