iPhone8的apple原装的无线applewatch充电器器和iPhoneX的原装无线applewatch充电器器一样吗(airpower

&&|&& &&|&&
浅谈ARM下U-boot给Kernel传参数
10:14:09 阅读:792
我们可能都知道:U-boot会给Linux Kernel传递很多参数,如:串口波特率,RAM Size,videofb、MAC Address等,而且Linux kernel也会读取和处理这些参数。
两者之间通过struct tag来传递参数。U-boot把要传递给kernel的东西保存在struct tag数据结构中,启动kernel时,把这个结构体的物理地址传给kernel;
Linux kernel通过这个地址,用parse_tags分析出传递过来的参数。
大家也知道在ARM架构上,u-boot向Linux内核传递参数利用了R0,R1和R2三个寄存器,并采用如下约定:
暂时不用,缺省放0
机器号,标识计算机系统的型号,
内核支持的所有使用ARM处理器的设备ID号定义在arch/arm/tools/mach-types文件中,
编译内核过程中会被转换为一个头文件include/asm-arm/mach-types.h供其他文件包含使用。
R2寄存器传递的是一个地址,也就是指针的概念,这个指针指向一个TAG区域.
UBOOT和Linux内核之间正是通过这个扩展了的TAG区域来进行复杂参数的传递,
如 command
line,文件系统信息等等,用户也可以扩展这个TAG来进行更多参数的传递。
下面就一下AM335X
SDK6.0的Code进行分析:
我们先了解这些参数是如何组织、如何传
先看U-boot阶段的存储的划分
要传递的参数上图中的SDRAM区域的什么位置?从上面的名字可以知道可定是GD区域,当然是怎么放,如何组织该这些数据,肯定要有一个相应的struct来保存,
这就是GD struct GD
struct的主要成员如下:
在U-Boot的include/asm-arm/global_data.h
global_data {
/* 与板子相关的结构,见下面 */
unsigned long
have_/* serial_init() was called */
unsigned long
/* Relocation Offset */
unsigned long
/* Address
of Environment struct */
unsigned long
/* Checksum of Environment valid? */
unsigned long
/* base address of frame buffer */
/* jump table */
u-boot是没有MMU,这都了解,而且U-boot有重载机制,可能肯定要对GD数据Modify,这就需要记住该地址,
uboot为了方便要访问GD,特点给提供一个存储寄存器
DECLARE_GLOBAL_DATA_PTR&&& register
volatile gd_t *gd asm ("r8")
DECLARE_GLOBAL_DATA_PTR定义一个gd_t全局数据结构的指针,这个指针存放在指定的寄存器r8中。
这个声明也避免编译器把r8分配给其它的变量。任何想要访问全局数据区的代码,
只要代码开头加入“DECLARE_GLOBAL_DATA_PTR”一行代码,然后就可以使用gd指针来访问全局数据区了。
根据U-Boot内存使用图中可以计算gd的值:
= TEXT_BASE -CONFIG_SYS_MALLOC_LEN - sizeof(gd_t)
这样uboot阶段Code都可以找到GD并Modify里面的内容
那现在就看uboot如何把组织的Data给Kernel Uboot
load kernel到memory中,
最长用的command是bootm bootm是Uboot启动内核的指令,它用来加载内核镜像,和go命令类似,但是支持r0,r1,r2和bootargs传递参数
bootm就是最终看到的函数是:
bootm的实现位于common/cmd_bootm.c中do_bootm函数
do_bootm的函数第一部分很简单,它首先查看环境变量"verify",如果不为"n",那么将对镜像进行Checksum的校验。
do_bootm可以接受一个可选参数,即镜像文件在内存中的地址。
如果没有指明addr,那么将使用默认的load_addr,它在早些时候被赋值为CFG_LOAD_ADDR。
再这里牵扯到Load地址,实际要加一个小插曲,就是我们Load的Kernel都是包含了一个Head,说白都是经过mkimage包装的,
当然AM335X的u-boot也是包装,因为他是两级Bootload,这不多说,就讲一下mkimage参数:
-n "Kernel 3.2.0" -A arm -O linux -T kernel -C none -a 80007fc0 -e
-d XXX.bin uImage 实际我吗只要只要-e这个参数就可以,
这个就是do_bootm()中的load地址 继续追踪,只要do_bootm()load的是一个有Head的Image,
当然要解析这个Image,到底是那个类型,335X要要注意,应该U-boot是mkiamge包过的原因,
如果是Kernel Image,根据类型判断,就要到目标了,
实际就是do_bootm_linux()函数 这个函数重要的原因就我们刚开始讲的U-boot和Kernel直接是通过TAG传递参数,
但是TAG实际是属于GD的,怎么样去设置TAG的参数去就这下面的参数里面写的很清楚了,重点看函数:
setup_start_tag (bd);
setup_serial_tag (?ms);
setup_revision_tag (?ms);
setup_end_tag (bd);
void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
ulong addr, ulong *len_ptr, int verify)
kernel_entry = (void (*)(int, int, uint))images-&
r2 = gd-&bd-&bi_boot_
kernel_entry(0, machid, r2);
其中要注意gd-&bd-&bi_boot_params 这个地址是在board_init()函数里面赋值的,
就是和你的Board以及很紧密,一般都在XXXXX.h中定义 一个值得注意的参数是theKernel,
它有三个参数,并且用它来指向系统镜像的入口地址,到现在总算把参数给放到制定的位置了,也是按照ARM的规范放到了
Kernel如何取参数和解析参数
Kernel取参数,我们就仅仅说明如何去取,就不讲如何解析这些参数了arch/arm/boot/compressed/head.S中的start入口(kernel code)
start,#function
@ save architecture ID
@ save atags pointer
到现在总算找到参数的地址了,下面就看按照约定好的套路去解析就可以了
整个解析的过程,我们仅仅说明函数的流程:
对于 Linux Kernel , ARM 平台启动时,先执行 arch/arm/kernel/head.S ,
此文件会调用 arch/arm/kernel/head-common.S 中的函数,并最后调用 start_kernel :
start_kernel
init/main.c 中的 start_kernel 函数中会调用 setup_arch 函数来处理各种平台相关的动作,
包括了 u-boot 传递过来参数的分析和保存
start_kernel()
setup_arch(&command_line);
setup_arch 函数在 arch/arm/kernel/setup.c 文件中实现
parse_cmdline(cmdline_p, from);
// 处理编译内核时指定的 cmdline 或 u-boot 传递的 cmdline
到目前位置可以说已经OK了,就不多说了
解析过程中主要是TAG,下面补充一下TAG的东西,就是对TAG的分类:
/* The list ends with an ATAG_NONE node. */
#define ATAG_NONE 0x
/* The list must start with an ATAG_CORE node */
#define ATAG_CORE 0x
/* it is allowed to have multiple ATAG_MEM nodes */
#define ATAG_MEM 0x
/* VGA text type displays */
#define ATAG_VIDEOTEXT 0x
/* describes how the ramdisk will be used in kernel */
#define ATAG_RAMDISK 0x
* this one accidentally used virtual addresses - as such,
* it's deprecated.
#define ATAG_INITRD 0x
/* describes where the compressed ramdisk image lives (physical address) */
#define ATAG_INITRD2 0x
/* board serial number. "64 bits should be enough for everybody" */
#define ATAG_SERIAL 0x
/* board revision */
#define ATAG_REVISION 0x
/* initial values for vesafb-type framebuffers. see struct screen_info
* in include/linux/tty.h
#define ATAG_VIDEOLFB 0x
/* command line: \0 terminated string */
#define ATAG_CMDLINE 0x
/* acorn RiscPC specific information */
#define ATAG_ACORN 0x
/* footbridge memory clock, see arch/arm/mach-footbridge/arch.c */
#define ATAG_MEMCLK 0x
后参与讨论
五湖四海皆朋友
阅读:11189
阅读:11233
阅读:10419
阅读:13694下次自动登录
现在的位置:
& 综合 & 正文
grub 内核启动参数(kernel command-line parameters)
kernel /boot/vmlinuz-2.6.32-279.el6.x86_64 ro root=UUID=866f85ce-0e10-424b-a329-19f3bebeacb8 nomodeset rd_NO_LUKS
KEYBOARDTYPE=pc KEYTABLE=us rd_NO_MD crashkernel=128M LANG=zh_CN.UTF-8 rd_NO_LVM rd_NO_DM rhgb quiet
ro: Mount the root device read-only on bootroot=UUID=866f85ce-0e10-424b-a329-19f3bebeacb8: Specify the root filesystem to boot from(/sbin/init 所在分区)nomodeset: Disable Kernel Mode Setting()
rd_NO_LUKS
KEYBOARDTYPE=pc
KEYTABLE=us
rd_NO_MDcrashkernel=128M: Reserve a portion of physical memory for kexec to use
LANG=zh_CN.UTF-8
rd_NO_DMrhgb: redhat graphical boot - This is a GUI mode booting screen with most of the information hidden while the user sees a rotating activity icon spining and brief information as to what the computer is doing.quiet: hides the majority of boot messages before rhgb starts. These are supposed to make the common user more comfortable. They get alarmed about seeing the kernel and initializing messages, so they hide them for their comfort.
启动时指定 run level
1(or single): single user mode
3: 文本界面,不启动图形界面(在文本界面中 startx 即可启动图形界面)
5: 图形界面
/etc/inittab
# Default runlevel. The runlevels used are:
0 - halt (Do NOT set initdefault to this)
1 - Single user mode
2 - Multiuser, without NFS (The same as 3, if you do not have networking)
3 - Full multiuser mode
4 - unused
6 - reboot (Do NOT set initdefault to this)
id:5:initdefault:
Google: "grub kernel boot parameters", "kernel command-line parameters"
Kernel Boot Command-LineParameter Reference
10 boot time parameters you should know about the Linux kernel
The Linux BootPrompt-HowTo,
Kernel boot command-line parameter reference: Chapter 9 - Linux Kernel in a Nutshell
Linux内核引导参数简介
&&&&推荐文章:
【上篇】【下篇】kernel-command-line
中文手册 [金步国]kernel-command-line 中文手册译者:版权声明本文译者是一位开源理念的坚定支持者,所以本文虽然不是软件,但是遵照开源的精神发布。无担保:本文译者不保证译文内容准确无误,亦不承担任何由于使用此文档所导致的损失。自由使用:任何人都可以自由的阅读/链接/打印此文档,无需任何附加条件。名誉权:任何人都可以自由的转载/引用/再创作此文档,但必须保留译者署名并注明出处。其他作品本文译者十分愿意与他人分享劳动成果,如果你对我的其他翻译作品或者技术文章有兴趣,可以在如下位置查看现有的作品集: [
]联系方式由于译者水平有限,因此不能保证译文内容准确无误。如果你发现了译文中的错误(哪怕是错别字也好),请来信指出,任何提高译文质量的建议我都将虚心接纳。Email(QQ):在QQ邮箱 .
systemd-231名称kernel-command-line — 内核引导选项大纲/proc/cmdline描述内核与 initrd(initial RAM disk)
以及基础用户空间功能,
可以通过内核引导选项在系统引导时配置。有关内核可以理解的引导选项,可以参考
文档。有关 initrd(initial RAM disk) 可以理解的引导参数,
可以参考对应发行版的相关文档以及
之类的文档。
核心参数systemd.unit=, rd.systemd.unit=, systemd.dump_core=, systemd.crash_chvt=, systemd.crash_shell=, systemd.crash_reboot=, systemd.confirm_spawn=, systemd.show_status=, systemd.log_target=, systemd.log_level=, systemd.log_color=, systemd.log_location=, systemd.default_standard_output=, systemd.default_standard_error=, systemd.setenv=, systemd.machine_id=控制 systemd 行为的参数,
手册。systemd.mask=, systemd.wants=, systemd.debug-shell传递给
分别用于在启动时:屏蔽指定的单元、启动指定的单元、
在 tty9 上启动一个调试SHELLsystemd.restore_state=作用于多个系统工具,
用于控制是否恢复到上一次系统运行时的状态。
quiet内核和 systemd 都理解此选项,
表示不在控制台上显示详细的日志消息。
手册。debug内核和 systemd 都理解此选项,
表示要在控制台上显示详细的调试信息。
手册。-b, rd.emergency, emergency, rd.rescue, rescue, single, s, S, 1, 2, 3, 4, 5出于兼容老旧系统而设置的参数,
仅能被 systemd 理解,详见
手册。locale.LANG=, locale.LANGUAGE=, locale.LC_CTYPE=, locale.LC_NUMERIC=, locale.LC_TIME=, locale.LC_COLLATE=, locale.LC_MONETARY=, locale.LC_MESSAGES=, locale.LC_PAPER=, locale.LC_NAME=, locale.LC_ADDRESS=, locale.LC_TELEPHONE=, locale.LC_MEASUREMENT=, locale.LC_IDENTIFICATION=控制本地化与语言的设置,
仅能被 systemd 理解。
手册。fsck.mode=, fsck.repair=
的参数。quotacheck.mode=
的参数。systemd.journald.forward_to_syslog=, systemd.journald.forward_to_kmsg=, systemd.journald.forward_to_console=, systemd.journald.forward_to_wall=
的参数。vconsole.keymap=, vconsole.keymap.toggle=, vconsole.font=, vconsole.font.map=, vconsole.font.unimap=
的参数。udev.log-priority=, rd.udev.log-priority=, udev.children-max=, rd.udev.children-max=, udev.exec-delay=, rd.udev.exec-delay=, udev.event-timeout=, rd.udev.event-timeout=, net.ifnames=
的参数。plymouth.enable=开启/关闭Plymouth启动画面,
手册。luks=, rd.luks=, luks.crypttab=, rd.luks.crypttab=, luks.name=, rd.luks.name=, luks.uuid=, rd.luks.uuid=, luks.options=, rd.luks.options=, luks.key=, rd.luks.key=设置LUKS全盘加密相关属性。
手册。fstab=, rd.fstab=设置是否应该使用 /etc/fstab 文件,
手册。root=, rootfstype=, rootflags=, ro, rw
设置根文件系统的相关信息:
所在设备、文件系统类型、挂载选项、只读挂载、读写挂载。
手册。systemd.gpt_auto=, rd.systemd.gpt_auto=设置是否应该开启GPT磁盘分区自动发现。
手册。覆盖 DefaultTimeoutStartSec= 的设置。
手册。modules-load=, rd.modules-load=指定在系统启动的早期就必须加载的内核模块。
手册。resume=指定从哪个设备中恢复休眠状态,
风格的设备表示法(fs_spec)。详见
手册。参见现在CPU2核以上比较普遍了,平时用linux上上网可能用不着双核甚至4核,大部分发行版内核都启用了CPU_HOTPLUG,到/sys/devices/system/cpu下可以看到文件夹cpu0、cpu1,除cpu0(这个不能关)每个文件夹下都有一个online文件,往里面写0就可以关闭核心,比如我的T7300,echo '0' &/sys/devices/system/cpu/cpu1/online 就可以关闭第二个核,不影响小型应用。要开启,往里面echo 1就行。这个操作好像要root完成,光有root权限还不够,必须先su而不能sudo,我这里是这样。然后你的主办支持甚至可以把关掉的CPU拆下来!当然一般桌面用户不会在几台机器间动态迁移CPU玩&&在启动grub参数kernel(grub1)或linux(2)后面加上maxcpus=N,就会启动前N个核心。这样子就有几个用处:1、关闭至1个或2个核,可以节约用电。 2、关闭所有奇数核,相当于禁用超线程但还有几个问题求解:1、关闭核心后,耗电是下来了,为什么电脑温度没什么变化呢? 2、如果是关闭后再开启核心,cpufreq就会失效,/sys/devices/system/cpu/cpuN/cpufreq/ 这个目录也会消失,只能以最高频率跑,卸载再重载acpi-cpufreq也没有用,求解如何调节CPU频率,谢谢!
-----------------------------------------------------------------------------------------------------------------------------
在Linux中,给kernel传递参数以控制其行为总共有三种方法:
1.build kernel之时的各个configuration选项。
2.当kernel启动之时,可以参数在kernel被GRUB或LILO等启动程序调用之时传递给kernel。
3.在kernel运行时,修改/proc或/sys目录下的文件。
这里我简单讲的就是第二种方式了,kernel在grub中配置的启动参数。
首先,kernel有哪些参数呢? 在linux的源代码中,有这样的一个文档Documentation/kernel-parameters.txt,它介绍了kernel的各个参数及其意义。
其次,kernel启动参数以空格分隔,而且是严格区分大小写的(如:mem和MEM是不一样的)。
再次,对于module特有的kernel参数写法是这样的,[module name].[parameter=XX],例如,igb.max_vfs=7这个kernel启动参数的效果就是相当于这样来动态加载module: modprobe igb max_vfs=7
另外,kernel是怎样处理这些启动参数的呢? 启动参数通常是这样的形式: name[=value_1][,value_2]&[,value_10]
&name&是关键字,内核用它来识别应该把&关键字&后面的值传递给谁,也就是如何处理这个值,是传递给处理进程还是作为环境变量或者抛给&init&。值的个数限制为10,你可以通过再次使用该关键字使用超过10个的参数。 首先,kernel检查关键字是不是 &root=&, &nfsroot=&, &nfsaddrs=&, &ro&, &rw&, &debug&或&init&,然后内核在bootsetups数组里搜索于该关键字相关联的已注册的处理函数,如果找到相关的已注册的处理函数,则调用这些函数并把关键字后面的值作为参数传递给这些函数。比如,你在启动时设置参数name=a,b,c,d,内核搜索bootsetups数组,如果发现&name&已注册,则调用&name&的设置函数如name_setup(),并把a,b,c,d传递给name_setup()执行。 所有型如&name=value&参数,如果没有被上面所述的设置函数接收,将被解释为系统启动后的环境变量,比如&TERM=vt100&P启动参数就会被作为一个启动后的环境变量。所有没有被内核设置函数接收也没又被设置成环境变量的参数都将留给init进程处理,比如&single&。
下面简单总结一下我在工作中常用到的一些kernel启动参数吧。
根磁盘相关启动参数:
root #指出启动的根文件系统 如:root=/dev/sda1
ro #指定根设备在启动过程中为read-only,默认情况下一般都是这样配的
rw #和ro类似,它是规定为read-write,可写
rootfstype #根文件系统类型,如:rootfstype=ext4
Console和kernel log相关启动参数:
console #console的设备和选项,如:console=tty0 console=ttyS0
debug #enable kernel debugging 启动中的所有debug信息都会打印到console上
quiet #disable all log messages 将kernel log level设置为KERN_WARNING,在启动中只非常严重的信息
loglevel #设置默认的console日志级别,如:loglevel=7 (0~7的数字分别为:KERN_EMERG,..,KERN_DEBUG)
time #设置在每条kernel log信息前加一个时间戳
内存相关的启动参数:
mem #指定kernel使用的内存量,mem=n[KMG]
hugepages #设置大页表页(4MB大小)的最多个数,hugepages=n
CPU相关的启动参数:
mce # Enable the machine check exception feature.
nosmp #Run as a single-processor machine. 不使用SMP(多处理器)
max_cpus #max_cpus=n, SMP系统最多能使用的CPU个数(即使系统中有大于n个的CPU)
Ramdisk相关的启动参数:
initrd #指定初始化ramdisk的位置,initrd=filename
noinitrd #不使用initrd的配置,即使配置了initrd参数
初始化相关启动参数:
init #在启动时去执行的程序,init=filename,默认值为/sbin/init
PCI相关的启动参数:
pci #pci相关的选项,我常使用pci=assign_buses,也使用过pci=nomsi
SELinux相关启动参数:
enforcing #SELinux enforcing状态的开关,enforcing=0表示仅仅是记录危险而不是阻止访问,enforcing=1完全enable,默认值是0
selinux #在启动时关闭或开启SELinux,selinux=0表示关闭,selinux=1表示开启selinux
另外,还是用max_loop来指定最多可使用的回路设备。
在Redhat的系统中,还有个经常看到的kernel启动参数&&rhgb,rhgb表示redhat graphics boot,就是会看到图片来代替启动过程中显示的文本信息,这些信息在启动后用dmesg也可以看到rhgb = redhat graphical boot & This is a GUI mode booting screen with most of the information hidden while the user sees a rotating activity icon spining and brief information as to what the computer is doing.
quiet = hides the majority of boot messages before rhgb starts. These are supposed to make the common user more comfortable. They get alarmed about seeing the kernel and initializing messages, so they hide them for their comfort.
linux kernel documents
《Linux kernel in a nutshell》
-------------------------------------------------------------------------------------------------------------------------------
kernel /vmlinuz-2.6.18-164.el5 ro root=/dev/VolGroup00/LogVol00 rhgb quiet maxcpus=4 mem=1024M initrd /initrd-2.6.18-164.el5.img
则reboot后启动的核数为4 (
nosmp 和 maxcpus=N
仅当定义了 __SMP__,该选项才可用。可以用来禁用多CPU或者指明最多支持的CPU个数。
),mem为1024M
阅读(...) 评论()linux kernel的cmdline参数解析原理分析
我的图书馆
linux kernel的cmdline参数解析原理分析
http://blog.csdn.net/skyflying2012/article/details/
4796人阅读
linux kernel(89)
版权声明:本文为博主原创文章,未经博主允许不得转载。
利用工作之便,今天研究了kernel下cmdline参数解析过程,记录在此,与大家共享,转载请注明出处,谢谢。
Kernel 版本号:3.4.55
Kernel启动时会解析cmdline,然后根据这些参数如console root来进行配置运行。
Cmdline是由bootloader传给kernel,如uboot,将需要传给kernel的参数做成一个tags链表放在ram中,将首地址传给kernel,kernel解析tags来获取cmdline等信息。
Uboot传参给kernel以及kernel如何解析tags可以看我的另一篇博文,链接如下:
http://blog.csdn.net/skyflying2012/article/details/
今天要分析的是kernel在获取到cmdline之后如何对cmdline进行解析。
依据我的思路(时间顺序,如何开始,如何结束),首先看kernel下2种参数的注册。
第一种是kernel通用参数,如console=ttyS0,115200 &root=/rdinit/init等。这里以console为例。
第二种是kernel下各个driver中需要的参数,在写driver中,如果需要一些启动时可变参数。可以在driver最后加入module_param()来注册一个参数,kernel启动时由cmdline指定该参数的值。
这里以drivers/usb/gadget/serial.c中的use_acm参数为例(这个例子有点偏。。因为最近在调试usb虚拟串口)
一 kernel通用参数
对于这类通用参数,kernel留出单独一块data段,叫.ini.setup段。在arch/arm/kernel/vmlinux.lds中:
&span&style="font-size:14"&.init.data&:&{&&&&*(.init.data)&*(.cpuinit.data)&*(.meminit.data)&*(.init.rodata)&*(.cpuinit.rodata)&*(.meminit.rodata)&.&=&ALIGN(32);&__dtb_star&&&&span&style="color:#333333;background-color:&rgb(255,&255,&0);"&&.&=&ALIGN(16);&__setup_start&=&.;&*(.init.setup)&__setup_end&=&.;&/span&&&&&__initcall_start&=&.;&*(.initcallearly.init)&__initcall0_start&=&.;&*(.initcall0.init)&*(.initcall0s.init)&__initcall1_start&=&&&&__con_initcall_start&=&.;&*(.con_initcall.init)&__con_initcall_end&=&.;&&&&__security_initcall_start&=&.;&*(.security_initcall.init)&__security_initcall_end&=&.;&&&&.&=&ALIGN(4);&__initramfs_start&=&.;&*(.init.ramfs)&.&=&ALIGN(8);&*(.)&&&}&/span&&&
可以看到段起始和结束。
.init.setup段中存放的就是通用参数和对应处理函数的映射表。在中
&span&style="font-size:14"&struct&obs_kernel_param&{&&&&&&const&char&*&&&&&&int&(*setup_func)(char&*);&&&&&&int&&&};&&&&/*&&*&Only&for&really&core&code.&&See&moduleparam.h&for&the&normal&way.&&*&&*&Force&the&alignment&so&the&compiler&doesn't&space&elements&of&the&&*&obs_kernel_param&"array"&too&far&apart&in&.init.setup.&&*/&&#define&__setup_param(str,&unique_id,&fn,&early)&&&&&&&&&&&&\&&&&&&static&const&char&__setup_str_##unique_id[]&__initconst&\&&&&&&&&&&__aligned(1)&=&&\&&&&&&static&struct&obs_kernel_param&__setup_##unique_id&&\&&&&&&&&&&__used&__section(.init.setup)&&&&&&&&&&&\&&&&&&&&&&__attribute__((aligned((sizeof(long)))))&&&&\&&&&&&&&&&=&{&__setup_str_##unique_id,&fn,&early&}&&&&#define&__setup(str,&fn)&&&&&&&&&&&&&&&&&&&&\&&&&&&__setup_param(str,&fn,&fn,&0)&&/*&NOTE:&fn&is&as&per&module_param,&not&__setup!&&Emits&warning&if&fn&&*&returns&non-zero.&*/&&#define&early_param(str,&fn)&&&&&&&&&&&&&&&&&&&&\&&&&&&__setup_param(str,&fn,&fn,&1)&/span&&&
可以看出宏定义以及定义了结构体,该结构体存放参数和对应处理函数,存放在段中。
可以想象,如果多个文件中调用该宏定义,在链接时就会根据链接顺序将定义的放到段中。
以为例,在中,如下:
&span&style="font-size:14"&static&int&__init&console_setup(char&*str)&&{&&.......&&}&&__setup("console=",&console_setup);&/span&&&
__setup宏定义展开,如下:
&span&style="font-size:14"&Static&struct&obs_kernel_param&__setup_console_setup&&&__used_section(.init.setup)&__attribute__((aligned((sizeof(long))))&=&{&&.name&=&“console=”,&&.setup_func&=&console_setup,&&.early&=&0&&}&/span&&&
__setup_console_setup编译时就会链接到段中,运行时就会根据中的参数名与段中的对比。
匹配则调用来解析该参数,的参数就是中的值,这是后面参数解析的大体过程了。
二&自定义参数
对于自定义参数,留出段一部分,叫段,在中,如下:
&span&style="font-size:14"&__param&:&AT(ADDR(__param)&-&0)&{&__start___param&=&.;&*(__param)&__stop___param&=&.;&}&/span&&&
该段放在段中。
那该段中存放的是什么样的数据呢?
Driver中使用来注册参数,跟踪这个宏定义,最终就会找到对段的操作函数如下:
&span&style="font-size:14"&/*&This&is&the&fundamental&function&for&registering&boot/module&&&&parameters.&*/&&#define&__module_param_call(prefix,&name,&ops,&arg,&perm,&level)&&&&\&&&&&&/*&Default&value&instead&of&permissions?&*/&&&&&&&&&\&&&&&&static&int&__param_perm_check_##name&__attribute__((unused))&=&&\&&&&&&BUILD_BUG_ON_ZERO((perm)&&&0&||&(perm)&&&0777&||&((perm)&&&2))&&\&&&&&&+&BUILD_BUG_ON_ZERO(sizeof(""prefix)&&&MAX_PARAM_PREFIX_LEN);&&&\&&&&&&static&const&char&__param_str_##name[]&=&prefix&#&&&&&&\&&&&&&static&struct&kernel_param&__moduleparam_const&__param_##name&&&\&&&&&&__used&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&\&&&&&&__attribute__&((unused,__section__&("__param"),aligned(sizeof(void&*))))&\&&&&&&=&{&__param_str_##name,&ops,&perm,&level,&{&arg&}&}&&........&&#define&module_param(name,&type,&perm)&&&&&&&&&&&&&&\&&&&&&module_param_named(name,&name,&type,&perm)&&&&#define&module_param_named(name,&value,&type,&perm)&&&&&&&&&&&&\&&&&&&param_check_##type(name,&&(value));&&&&&&&&&&&&&&&&\&&&&&&module_param_cb(name,&?m_ops_##type,&&value,&perm);&&&&&&&&\&&&&&&__MODULE_PARM_TYPE(name,&#type)&&&&#define&module_param_cb(name,&ops,&arg,&perm)&&&&&&&&&&&&&&&&&&&&&\&&&&&&__module_param_call(MODULE_PARAM_PREFIX,&name,&ops,&arg,&perm,&-1)&/span&&&
以中的为例,如下:
&span&style="font-size:14"&static&bool&use_acm&=&true;&&module_param(use_acm,&bool,&0);&/span&&&
Module_param展开到,如下:
&span&style="font-size:14"&Static&bool&use_acm&=&true;&&Param_check_bool(use_acm,&&(use_acm));&&__module_param_call(MODULE_PARAM_PREFIX,&use_acm,&?m_ops_bool,&&(use_acm,&0,&-1));&&__MODULE_PARAM_TYPE(use_acm,&bool);&/span&&&
将展开,可以看到是定义了结构体,如下:
&span&style="font-size:14"&Static&struct&kernel_param&__moduleparam_const&__param_use_acm&&&&__used&&&__attribute__&((unused,__section__&("__param"),aligned(sizeof(void&*))))&=&{&&.name&=&MODULE_PARAM_PREFIX#use_acm,&&.ops&=&?m_ops_bool,&&.Perm=0,&&.level&=&-1.&&.arg&=&&use_acm&&}&/span&&&
很清楚,跟段一样,链接时会根据链接顺序将定义的放在段中。
Kernel_param有个成员变量需要注意:
ops=param_ops_bool,是结构体,定义如下:
struct&kernel_param_ops&param_ops_bool&=&{&&&&&&.set&=&param_set_bool,&&&&&&.get&=&param_get_bool,&&};&&
这个成员函数分别去设置和获取参数值
在中可以看到默认支持的driver参数类型有(字符串)&(字符串指针)等。
对于默认支持的参数类型,中提供了来处理相应类型的参数。
Arg&=&&use_acm,宏定义展开,可以看到中存放的地址。参数设置函数()
将值设置到地址上,也就是改变了的值,从而到达传递参数的目的。
.name=MODULE_PARAM_PREFIX#use_acm,定义了该的。
MODULE_PARAM_PREFIX非常重要,定义在中:
*&You&can&override&this&manually,&but&generally&this&should&match&the&&&&&module&name.&*/&&#ifdef&MODULE&&#define&MODULE_PARAM_PREFIX&/*&empty&*/&&#else&&#define&MODULE_PARAM_PREFIX&KBUILD_MODNAME&"."&&#endif&&
如果我们是模块编译(make modules),则为。
在模块传参时,参数名为use_acm,如insmod g_serial.ko use_acm=0
正常编译,为模块名”.”
所以这里将编入,根据,如下:
g_serial-y&&&&&&&&&&:=&serial.o&&....&&obj-$(CONFIG_USB_G_SERIAL)&&+=&g_serial.o&&
最终是生成,模块名为。。
kernel传参时,该参数名为g_serial.use_acm
这样处理防止下众多中出现重名的参数。
可以看出,对于注册的参数,如果是默认支持类型,会提供参数处理函数。
如果不是支持参数类型,则需要自己去实现了。
这个可以看中的参数的注册(又有点偏。。。无意间找到的)。
参数注册是在编译链接时完成的(链接器将定义结构体放到或中)
接下来需要分析启动时如何对传入的进行分析。
三 kernel对cmdline的解析
根据我之前写的博文可知,中中解析获取,拷贝到中。我们接着往下看。
调用,将拷贝份,放在。
下面调用如下:
&span&style="font-size:14"&void&__init&parse_early_options(char&*cmdline)&&{&&&&&&parse_args("early&options",&cmdline,&NULL,&0,&0,&0,&do_early_param);&&}&&&&/*&Arch&code&calls&this&early&on,&or&if&not,&just&before&other&parsing.&*/&&void&__init&parse_early_param(void)&&{&&&&&&static&__initdata&int&done&=&0;&&&&&&static&__initdata&char&tmp_cmdline[COMMAND_LINE_SIZE];&&&&&&&&if&(done)&&&&&&&&&&return;&&&&&&&&/*&All&fall&through&to&do_early_param.&*/&&&&&&strlcpy(tmp_cmdline,&boot_command_line,&COMMAND_LINE_SIZE);&&&&&&parse_early_options(tmp_cmdline);&&&&&&done&=&1;&&}&&Parse_early_param拷贝cmdline到tmp_cmdline中一份,最终调用parse_args,如下:&&&&/*&Args&looks&like&"foo=bar,bar2&baz=fuz&wiz".&*/&&int&parse_args(const&char&*name,&&&&&&&&&&&&&char&*args,&&&&&&&&&&&&&const&struct&kernel_param&*params,&&&&&&&&&&&&&unsigned&num,&&&&&&&&&&&&&s16&min_level,&&&&&&&&&&&&&s16&max_level,&&&&&&&&&&&&&int&(*unknown)(char&*param,&char&*val))&&{&&&&&&char&*param,&*&&&&&&&&pr_debug("Parsing&ARGS:&%s\n",&args);&&&&&&&&/*&Chew&leading&spaces&*/&&&&&&args&=&skip_spaces(args);&&&&&&&&while&(*args)&{&&&&&&&&&&int&&&&&&&&&&&int&irq_was_&&&&&&&&&&&&args&=&next_arg(args,&?m,&&val);&&&&&&&&&&irq_was_disabled&=&irqs_disabled();&&&&&&&&&&ret&=&parse_one(param,&val,&params,&num,&&&&&&&&&&&&&&&&&&min_level,&max_level,&unknown);&&&&&&&&&&if&(irq_was_disabled&&&&!irqs_disabled())&{&&&&&&&&&&&&&&printk(KERN_WARNING&"parse_args():&option&'%s'&enabled&"&&&&&&&&&&&&&&&&&&&&&&"irq's!\n",&param);&&&&&&&&&&}&&&&&&&&&&switch&(ret)&{&&&&&&&&&&case&-ENOENT:&&&&&&&&&&&&&&printk(KERN_ERR&"%s:&Unknown&parameter&`%s'\n",&&&&&&&&&&&&&&&&&&&&&name,&param);&&&&&&&&&&&&&&return&&&&&&&&&&&case&-ENOSPC:&&&&&&&&&&&&&&printk(KERN_ERR&&&&&&&&&&&&&&&&&&&&&"%s:&`%s'&too&large&for&parameter&`%s'\n",&&&&&&&&&&&&&&&&&&&&&name,&val&?:&"",&param);&&&&&&&&&&&&&&return&&&&&&&&&&&case&0:&&&&&&&&&&&&&&break;&&&&&&&&&&default:&&&&&&&&&&&&&&printk(KERN_ERR&&&&&&&&&&&&&&&&&&&&&"%s:&`%s'&invalid&for&parameter&`%s'\n",&&&&&&&&&&&&&&&&&&&&&name,&val&?:&"",&param);&&&&&&&&&&&&&&return&&&&&&&&&&&}&&&&&&}&&&&&&&&/*&All&parsed&OK.&*/&&&&&&return&0;&&}&&.....&&void&__init&parse_early_options(char&*cmdline)&&{&&&&&&parse_args("early&options",&cmdline,&NULL,&0,&0,&0,&do_early_param);&&}&/span&&&
Parse_args遍历,按照空格切割获取参数,对所有参数调用获取参数名和参数值。如,则,。调用。如下:
&span&style="font-size:14"&static&int&parse_one(char&*param,&&&&&&&&&&&&&&&char&*val,&&&&&&&&&&&&&&&const&struct&kernel_param&*params,&&&&&&&&&&&&&&&unsigned&num_params,&&&&&&&&&&&&&&&s16&min_level,&&&&&&&&&&&&&&&s16&max_level,&&&&&&&&&&&&&&&int&(*handle_unknown)(char&*param,&char&*val))&&{&&&&&&unsigned&int&i;&&&&&&int&&&&&&&&&/*&Find&parameter&*/&&&&&&for&(i&=&0;&i&&&num_&i++)&{&&&&&&&&&&if&(parameq(param,&params[i].name))&{&&&&&&&&&&&&&&if&(params[i].level&&&min_level&&&&&&&&&&&&&&&&&&||&params[i].level&&&max_level)&&&&&&&&&&&&&&&&&&return&0;&&&&&&&&&&&&&&/*&No&one&handled&NULL,&so&do&it&here.&*/&&&&&&&&&&&&&&if&(!val&&&&params[i].ops-&set&!=&param_set_bool&&&&&&&&&&&&&&&&&&&&&params[i].ops-&set&!=&param_set_bint)&&&&&&&&&&&&&&&&&&return&-EINVAL;&&&&&&&&&&&&&&pr_debug("They&are&equal!&&Calling&%p\n",&&&&&&&&&&&&&&&&&&&&&params[i].ops-&set);&&&&&&&&&&&&&&mutex_lock(?m_lock);&&&&&&&&&&&&&&err&=&params[i].ops-&set(val,&?ms[i]);&&&&&&&&&&&&&&mutex_unlock(?m_lock);&&&&&&&&&&&&&&return&&&&&&&&&&&}&&&&&&}&&&&&&&&if&(handle_unknown)&{&&&&&&&&&&pr_debug("Unknown&argument:&calling&%p\n",&handle_unknown);&&&&&&&&&&return&handle_unknown(param,&val);&&&&&&}&&&&&&&&pr_debug("Unknown&argument&`%s'\n",&param);&&&&&&return&-ENOENT;&&}&/span&&&
由于从传入的,所以是直接走的最后函数。该函数是由传入的。如下:
&span&style="font-size:14"&static&int&__init&do_early_param(char&*param,&char&*val)&&{&&&&&&const&struct&obs_kernel_param&*p;&&&&&&&&for&(p&=&__setup_&p&&&__setup_&p++)&{&&&&&&&&&&if&((p-&early&&&&parameq(param,&p-&str))&||&&&&&&&&&&&&&&(strcmp(param,&"console")&==&0&&&&&&&&&&&&&&&&&&strcmp(p-&str,&"earlycon")&==&0)&&&&&&&&&&)&{&&&&&&&&&&&&&&if&(p-&setup_func(val)&!=&0)&&&&&&&&&&&&&&&&&&printk(KERN_WARNING&&&&&&&&&&&&&&&&&&&&&&&&&"Malformed&early&option&'%s'\n",&param);&&&&&&&&&&}&&&&&&}&&&&&&/*&We&accept&everything&at&this&stage.&*/&&&&&&return&0;&&}&/span&&&
Do_early_param遍历段,如果有的为,或中有参数并且有参数,则会调用该的函数来解析参数。
Do_early_param会对中优先级较高的参数进行解析。我翻了下源码找到一个例子,就是,利用参数来注册最早的一个,有兴趣大家可以参考下。
如果想启动中尽早打印输出,方便调试,可以注册为的。
在其参数处理函数中,注册一个早期的,从而是信息正常打印,这个在后面我还会总结一篇打印机制来说这个问题。
是为中需要尽早配置的功能(如)做的解析。
Do_early_param就说道这里,该函数并没有处理我们经常使用的通用参数和自定义参数。接着往下看。代码如下:
&span&style="font-size:14"&&&&&setup_arch(&command_line);&&&&&&mm_init_owner(&init_mm,&&init_task);&&&&&&mm_init_cpumask(&init_mm);&&&&&&setup_command_line(command_line);&&&&&&setup_nr_cpu_ids();&&&&&&setup_per_cpu_areas();&&&&&&smp_prepare_boot_cpu();&/*&arch-specific&boot-cpu&hooks&*/&&&&&&&&build_all_zonelists(NULL);&&&&&&page_alloc_init();&&&&&&&&printk(KERN_NOTICE&"Kernel&command&line:&%s\n",&boot_command_line);&&&&&&parse_early_param();&&&&&&parse_args("Booting&kernel",&static_command_line,&__start___param,&&&&&&&&&&&&&__stop___param&-&__start___param,&&&&&&&&&&&&&-1,&-1,&&unknown_bootoption);&/span&&&
Parse_early_param结束后,调用了。这次调用,不像中调用那样指针都为,而是指定了段。
回到上面看函数,参数为段起始地址,为个数。
Min_level,max_level都为
Parse_args还是像之前那样,遍历,分割获取每个参数的和,对每个参数调用。
回看函数源码:
()首先会遍历段中所有将其与参数的对比,同名则调用该成员变量的方法来设置参数值。
联想前面讲自定义参数例子中有参数,则在中遍历匹配在中注册的,调用的函数,从而设置
(2)如果传给是通用参数,如。则前面遍历段不会找到匹配的。就走到后面调用。就是传来的,代码如下:&
&span&style="font-size:14"&/*&&*&Unknown&boot&options&get&handed&to&init,&unless&they&look&like&&*&unused&parameters&(modprobe&will&find&them&in&/proc/cmdline).&&*/&&static&int&__init&unknown_bootoption(char&*param,&char&*val)&&{&&&&&&repair_env_string(param,&val);&&&&&&&&/*&Handle&obsolete-style&parameters&*/&&&&&&if&(obsolete_checksetup(param))&&&&&&&&&&return&0;&&&&&&&&/*&Unused&module&parameter.&*/&&&&&&if&(strchr(param,&'.')&&&&(!val&||&strchr(param,&'.')&&&val))&&&&&&&&&&return&0;&&&&&&&&if&(panic_later)&&&&&&&&&&return&0;&&&&&&&&if&(val)&{&&&&&&&&&&/*&Environment&option&*/&&&&&&&&&&unsigned&int&i;&&&&&&&&&&for&(i&=&0;&envp_init[i];&i++)&{&&&&&&&&&&&&&&if&(i&==&MAX_INIT_ENVS)&{&&&&&&&&&&&&&&&&&&panic_later&=&"Too&many&boot&env&vars&at&`%s'";&&&&&&&&&&&&&&&&&&panic_param&=&&&&&&&&&&&&&&&}&&&&&&&&&&&&&&if&(!strncmp(param,&envp_init[i],&val&-&param))&&&&&&&&&&&&&&&&&&break;&&&&&&&&&&}&&&&&&&&&&envp_init[i]&=&&&&&&&}&else&{&/span&&&[cpp]
&span&style="font-size:14"&&&&&&&&&/*&Command&line&option&*/&&&&&&&&&&unsigned&int&i;&&&&&&&&&&for&(i&=&0;&argv_init[i];&i++)&{&&&&&&&&&&&&&&if&(i&==&MAX_INIT_ARGS)&{&&&&&&&&&&&&&&&&&&panic_later&=&"Too&many&boot&init&vars&at&`%s'";&&&&&&&&&&&&&&&&&&panic_param&=&&&&&&&&&&&&&&&}&&&&&&&&&&}&&&&&&&&&&argv_init[i]&=&&&&&&&}&&&&&&return&0;&&}&/span&&&
首先会将重新组合为形式。
Obsolete_checksetup则遍历段所有,如有与匹配,则调用进行参数值配置。
这里需要注意的一点是将重新拼成了形式。后面遍历匹配都是匹配的而不是。
如之前分析通用参数所举例子,。
Console=ttyS0,是匹配前面,如果匹配,则跳过,获取到其值,调用其具体的函数来解析设置参数值。
可以想象,对于传来的每一个参数都会将以及段遍历匹配,匹配到或一致,则调用其相应的或函数进行参数值解析或设置。
Start_kernel中结束,的就解析完成!
总结下的参数解析:
()编译链接,利用段将所需参数(driver及通用)和对应处理函数的映射表(结构体)存放起来。
()启动,处理早期使用的参数(如)
(3)对每个参数都遍历进行匹配,匹配成功,则调用对应处理函数进行参数值的解析和设置。
还有一点很值得思考,kernel下对于这种映射处理函数表方式还有很多使用。比如之前博文中uboot传参给kernel,kernel对于不同tags的处理函数也是以该种方式来映射的。
kernel下driver私有结构体的回调处理函数也有这个思想哇!
TA的最新馆藏

我要回帖

更多关于 apple care 充电器 的文章

 

随机推荐