Linux/Android启动 之 (module_init和machine-init函数)

本文解析了Linux/Android启动过程中的Machine-Init函数作用及其初始化流程。详细介绍了Machine-Init函数如何在内核启动过程中被调用,以及它与其他驱动初始化的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

                             Linux/Android启动之Machine-Init函数


基础知识

1. Linux启动过程中驱动模块初始化的位置

Linux OS的启动过程中将会去创建线程kernel_init,该线程负责Driver初始化等一系列工作。线程kernel_init将会依次调用do_basic_setup() -->do_initcalls()-->do_one_initcall(),并在do_initcalls()中完成对各个驱动模块Init函数的调用。

这部分代码如下:

函数do_basic_setup()如下:

/*

* Ok, the machine is now initialized. None of the devices

* have been touched yet, but the CPU subsystem is up and

* running, and memory and process management works.

*

* Now we can finally start doing some real work..

*/

static void __init do_basic_setup(void)

{

rcu_init_sched(); /* needed by module_init stage. */

init_workqueues();

usermodehelper_init();

driver_init();

init_irq_proc();

do_initcalls();

}

函数do_initcalls()

extern initcall_t __initcall_start[], __initcall_end[], __early_initcall_end[];

static void __init do_initcalls(void)

{

initcall_t *call;

 

for (call = __early_initcall_endcall < __initcall_endcall++)

do_one_initcall(*call);

 

/* Make sure there is no pending stuff from the initcall sequence */

flush_scheduled_work();

}

函数do_one_initcall(initcall_t fn)如下:

int initcall_debug;

core_param(initcall_debuginitcall_debugbool, 0644);

 

int do_one_initcall(initcall_t fn)

{

int count = preempt_count();

ktime_t calltimedeltarettime;

char msgbuf[64];

struct boot_trace_call call;

struct boot_trace_ret ret;

 

if (initcall_debug) {

call.caller = task_pid_nr(current);

printk("calling  %pF @ %i/n"fncall.caller);

calltime = ktime_get();

trace_boot_call(&callfn);

enable_boot_trace();

}

 

ret.result = fn();

 

if (initcall_debug) {

disable_boot_trace();

rettime = ktime_get();

delta = ktime_sub(rettimecalltime);

ret.duration = (unsigned long longktime_to_ns(delta) >> 10;

trace_boot_ret(&retfn);

printk("initcall %pF returned %d after %Ld usecs/n"fn,

ret.resultret.duration);

}

 

msgbuf[0] = 0;

 

if (ret.result && ret.result != -ENODEV && initcall_debug)

sprintf(msgbuf"error code %d "ret.result);

 

if (preempt_count() != count) {

strlcat(msgbuf"preemption imbalance "sizeof(msgbuf));

preempt_count() = count;

}

if (irqs_disabled()) {

strlcat(msgbuf"disabled interrupts "sizeof(msgbuf));

local_irq_enable();

}

if (msgbuf[0]) {

printk("initcall %pF returned with %s/n"fnmsgbuf);

}

 

return ret.result;

}

二.Machine-Init函数被调用的位置

Machine-Init也将在函数do_one_initcall(initcall_t fn)中伴随其它的模块Init函数一起被调用,不同之处在于Machine-Init的优先级最高。

三.Machine-Init函数相关解析过程

1. 重要结构体machine_desc说明

struct machine_desc描述了机器(machine, 也就是目标板)对内核初始阶段资源分配至关重要的一些参数。

首先,来看一下结构体machine_des的内容(在arch/arm/include/asm/mach/arch.h):

struct machine_desc {

/*

* Note! The first four elements are used

* by assembler code in head.S, head-common.S

*/

unsigned intnr;/* architecture number,记录体系结构*/

unsigned intphys_io;/* start of physical io*/

unsigned intio_pg_offst;/* byte offset for io 

* page tabe entry*/

 

const char*name;/* architecture name,体系结构名字*/

unsigned longboot_params;/* tagged list*/

 

unsigned intvideo_start;/* start of video RAM*/

unsigned intvideo_end;/* end of video RAM*/

 

unsigned intreserve_lp0 :1;/* never has lp0*/

unsigned intreserve_lp1 :1;/* never has lp1*/

unsigned intreserve_lp2 :1;/* never has lp2*/

unsigned intsoft_reboot :1;/* soft reboot*/

void(*fixup)(struct machine_desc *,

struct tag *, char **,

struct meminfo *);

void(*map_io)(void);/* IO mapping function*/

void(*init_irq)(void);

struct sys_timer*timer;/* system tick timer*/

void(*init_machine)(void);

};

其中,结构体的最后一个函数指针init_machine会被初始化为Machine-Init的地址。系统中针对每一种CPU都会去定义一个该结构体变量,并在系统的启动过程中进行引用。

2. 对应当前开发板的结构体machine_des的初始化

这里以Samsung S3C6410的开发板的BSP为例来进行分析。

Samsung S3C6410machine_des在文件arch/arm/mach-s3c6410/mach-s3c6410.c中定义,定义方式如下:

MACHINE_START(SMDK6410"SMDK6410")

/* Maintainer: Ben Dooks <ben@fluff.org> */

.phys_ioS3C_PA_UART & 0xfff00000,

.io_pg_offst= (((u32)S3C_VA_UART) >> 18) & 0xfffc,

.boot_paramsS3C64XX_PA_SDRAM + 0x100,

.fixupsmdk6410_fixup,

.init_irqs3c6410_init_irq,

.map_iosmdk6410_map_io,

.init_machinesmdk6410_machine_init,

#ifndef CONFIG_HIGH_RES_TIMERS

.timer= &s3c64xx_timer,

#else

.timer= &sec_timer,

#endif /* CONFIG_HIGH_RES_TIMERS */

 

MACHINE_END

Linux中针对各个不同的CPU存在很多个类似于上面的定义,宏定义MACHINE_START的定义如下:

/*

* Set of macros to define architecture features.  This is built into

* a table by the linker.

*/

#define MACHINE_START(_type,_name)/

static const struct machine_desc __mach_desc_##_type/

__used/

__attribute__((__section__(".arch.info.init"))) = {/

.nrMACH_TYPE_##_type,/

.name_name,

 

#define MACHINE_END/

};

其实,宏定义替换后的就变成了如下的定义方式:

static const struct machine_desc__SMDK6410

__used

__attribute__((__section__(".arch.info.init"))) = { /*__section__指定了该结构体被链接的位置*/

 

.nr = MACH_TYPE_SMDK6410,

.name = "SMDK6410",

phys_ioS3C_PA_UART & 0xfff00000,

.io_pg_offst= (((u32)S3C_VA_UART) >> 18) & 0xfffc,

.boot_paramsS3C64XX_PA_SDRAM + 0x100,

.fixup= smdk6410_fixup,

.init_irq= s3c6410_init_irq,

.map_io= smdk6410_map_io,

.init_machine= smdk6410_machine_init,

#ifndef CONFIG_HIGH_RES_TIMERS

.timer= &s3c64xx_timer,

#else

.timer= &sec_timer,

};

3. 函数setup_machine实现对结构体machine_des的定位

函数setup_machine实现对结构体machine_des位置的判别,其代码代码如下:

static struct machine_desc * __init setup_machine(unsigned int nr)

{

struct machine_desc *list;

 

/*

* locate machine in the list of supported machines.可能支持多个cpu哦

*/

list = lookup_machine_type(nr);

if (!list) {

printk("Machine configuration botched (nr %d), unable "

"to continue./n"nr);

while (1);

}

 

printk("Machine: %s/n"list->name);

 

return list;

}

上面红色标记的函数lookup_machine_type(nr)用汇编实现在文件head.S中,用来查找对应当前设备的machine_desc变量位置,并通过函数lookup_machine_type指向其起始位置,后续就可以直接通过该返回值来对结构体machine_desc变量machine_desc__SMDK6410的值进行读取,如后面读取Machine-Init函数的指针的操作init_machine = mdesc->init_machine

而函数setup_machine被调用的过程如下(start_kernelàsetup_archàsetup_machine):

 

 

在函数setup_machine最后会在全局指针变量init_machine中记录Machine-Init函数的信息。

4. Machine-Init的导出

在文件arch/arm/kernel/setup.c中通过如下的方式将Machine-Init设置为模块的Init函数

static void (*init_machine)(void__initdata;

 

static int __init customize_machine(void)

{

/* customizes platform devices, or adds new ones */

if (init_machine)

init_machine();

return 0;

}

arch_initcall(customize_machine);

可能不太熟悉驱动优先级的人会觉得,这里怎么使用arch_initcall导出,而不是通常的module_init方式进行导出。

Linux中,没有办法像CE/Mobile中通过注册表的方式来定义驱动的优先级,只有通过导出函数的LevelMakefile中模块驱动书写的先后顺序来定义。

关于导出函数Level的代码如下:

#define __define_initcall(level,fn)   static initcall_t __initcall_##fn __attribute_used__   __attribute__((__section__(".initcall" level ".init"))) = fn

 

#define core_initcall(fn) __define_initcall("1",fn)

#define postcore_initcall(fn__define_initcall("2",fn)

#define arch_initcall(fn__define_initcall("3",fn)

#define subsys_initcall(fn__define_initcall("4",fn)

#define fs_initcall(fn__define_initcall("5",fn)

#define device_initcall(fn) __define_initcall("6",fn)

#define late_initcall(fn__define_initcall("7",fn)

 

#define __initcall(fn) device_initcall(fn)

 

#define __exitcall(fn)   static exitcall_t __exitcall_##fn __exit_call = fn

 

#define module_init(x) __initcall(x);

 

#define module_exit(x) __exitcall(x);

可以看到,通常驱动中采用的module_init优先级为6,也即最低优先级,而前面采用的arch_initcall优先级为最高优先级1

/* * 安装模块(带参数) * sudo insmod char_dev.ko cap=1 * * 查看设备节点 * ls -l /dev/my_char_dev * * 测试写入设备 * echo "Hello Kernel World" | sudo tee /dev/my_char_dev * * 查看内核日志(需要root权限) * sudo dmesg | grep my_char_dev * * 卸载模块 * sudo rmmod char_dev */ #include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/uaccess.h> #include <linux/ctype.h> #include <linux/mutex.h> #define DEVICE_NAME "my_char_dev" #define MAX_BUF_LEN 1024 /* 模块参数 */ static int cap = 0; module_param(cap, int, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(cap, "Convert to uppercase if set to 1 (default=0)"); /* 共享数据结构 */ static struct { char buffer[MAX_BUF_LEN]; // 数据缓冲区 size_t data_len; // 有效数据长度 struct mutex lock; // 互斥锁 } dev_data; static dev_t dev_num; static struct cdev my_cdev; static struct class *my_class; static struct device *my_device; /* 设备打开函数 */ static int device_open(struct inode *inode, struct file *file) { return 0; } /* 设备释放函数 */ static int device_release(struct inode *inode, struct file *file) { return 0; } /* 设备写入函数 */ static ssize_t device_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) { size_t len = (count > MAX_BUF_LEN - 1) ? MAX_BUF_LEN - 1 : count; int i = 0; // 加锁保护共享数据 mutex_lock(&dev_data.lock); /* 从用户空间复制数据 */ if (copy_from_user(dev_data.buffer, buf, len)) { mutex_unlock(&dev_data.lock); return -EFAULT; } dev_data.buffer[len] = '\0'; // 确保字符串终止 dev_data.data_len = len; // 记录有效长度 /* 根据cap参数进行大小写转换 */ if (cap) { for (i = 0; dev_data.buffer[i]; i++) { dev_data.buffer[i] = toupper(dev_data.buffer[i]); } } printk(KERN_INFO "my_char_dev: Received: %s\n", dev_data.buffer); mutex_unlock(&dev_data.lock); // 解锁 return len; } /* 设备读取函数 */ static ssize_t device_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { ssize_t retval = 0; // 加锁保护共享数据 mutex_lock(&dev_data.lock); if (*f_pos >= dev_data.data_len) { mutex_unlock(&dev_data.lock); return 0; // 文件结尾 } // 计算剩余可读数据量 size_t remaining = dev_data.data_len - *f_pos; size_t to_copy = (count < remaining) ? count : remaining; // 复制数据到用户空间 if (copy_to_user(buf, dev_data.buffer + *f_pos, to_copy)) { mutex_unlock(&dev_data.lock); return -EFAULT; } *f_pos += to_copy; // 更新文件位置 retval = to_copy; mutex_unlock(&dev_data.lock); // 解锁 return retval; } /* 定义设备支持的操作 */ static const struct file_operations fops = { .owner = THIS_MODULE, .open = device_open, .release = device_release, .write = device_write, .read = device_read, // 添加read支持 }; /* 模块初始化函数 */ static int __init char_dev_init(void) { /* 初始化共享数据 */ mutex_init(&dev_data.lock); // 初始化互斥锁 dev_data.data_len = 0; dev_data.buffer[0] = '\0'; /* 动态分配设备号 */ if (alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME) < 0) return -1; /* 初始化字符设备 */ cdev_init(&my_cdev, &fops); if (cdev_add(&my_cdev, dev_num, 1) < 0) goto err_cdev; /* 创建设备类 */ my_class = class_create(THIS_MODULE, "my_char_class"); if (IS_ERR(my_class)) goto err_class; /* 创建设备节点 */ my_device = device_create(my_class, NULL, dev_num, NULL, DEVICE_NAME); if (IS_ERR(my_device)) goto err_device; /* 设置设备权限(所有用户可读写) */ device_create_file(my_device, &dev_attr_cap); // 可选:添加cap属性文件 printk(KERN_INFO "my_char_dev: Module loaded, cap=%d\n", cap); return 0; /* 错误处理 */ err_device: class_destroy(my_class); err_class: cdev_del(&my_cdev); err_cdev: unregister_chrdev_region(dev_num, 1); return -1; } /* 模块退出函数 */ static void __exit char_dev_exit(void) { device_destroy(my_class, dev_num); class_destroy(my_class); cdev_del(&my_cdev); unregister_chrdev_region(dev_num, 1); mutex_destroy(&dev_data.lock); // 销毁互斥锁 printk(KERN_INFO "my_char_dev: Module unloaded\n"); } module_init(char_dev_init); module_exit(char_dev_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("Advanced char device driver with case conversion"); 这段代码make以后发生以下错误: xyc@xyc-virtual-machine:~/VSCode/embedded-linux/char_device$ make make -C /lib/modules/4.15.0-213-generic/build M=/home/xyc/VSCode/embedded-linux/char_device modules make[1]: Entering directory '/usr/src/linux-headers-4.15.0-213-generic' CC [M] /home/xyc/VSCode/embedded-linux/char_device/char_dev.o /home/xyc/VSCode/embedded-linux/char_device/char_dev.c: In function ‘device_read’: /home/xyc/VSCode/embedded-linux/char_device/char_dev.c:103:5: warning: ISO C90 forbids mixed declarations and code [-Wdeclaration-after-statement] size_t remaining = dev_data.data_len - *f_pos; ^~~~~~ /home/xyc/VSCode/embedded-linux/char_device/char_dev.c: In function ‘char_dev_init’: /home/xyc/VSCode/embedded-linux/char_device/char_dev.c:156:36: error: ‘dev_attr_cap’ undeclared (first use in this function); did you mean ‘setattr_copy’? device_create_file(my_device, &dev_attr_cap); // 可选:添加cap属性文件 ^~~~~~~~~~~~ setattr_copy /home/xyc/VSCode/embedded-linux/char_device/char_dev.c:156:36: note: each undeclared identifier is reported only once for each function it appears in scripts/Makefile.build:340: recipe for target '/home/xyc/VSCode/embedded-linux/char_device/char_dev.o' failed make[2]: *** [/home/xyc/VSCode/embedded-linux/char_device/char_dev.o] Error 1 Makefile:1596: recipe for target '_module_/home/xyc/VSCode/embedded-linux/char_device' failed make[1]: *** [_module_/home/xyc/VSCode/embedded-linux/char_device] Error 2 make[1]: Leaving directory '/usr/src/linux-headers-4.15.0-213-generic' Makefile:5: recipe for target 'all' failed make: *** [all] Error 2 这是什么问题
最新发布
08-08
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值