• U-Boot与kernel之间的参数传递机制完全解析


    Linux操作系统启动时,往往都基于通用bootloader为基础,由bootloader完成基本的硬件基础设施的初始化工作后,再将一个完整的硬件环境交付给kernel。而二者之间的信息传递又尤为关键,前者可以给后者传递其配置、硬件环境信息等。

    一、U-boot下的命令行处理

    U-Boot向kernel传递的命令行参数属于环境变量的一种,可以说,整个U-Boot的环境变量都是围绕着命令行参数展开的。因此,分析U-Boot下的命令行参数,首先分析环境变量的初始化及工作原理。

    文中所述基于U-Boot 2016.03,ARM 平台

    1. 环境变量

    1.1 初始化

    1.1.1 env_init

    初始化函数为env_init(),在board_init_f中调用。在最新版本的u-boot中,env_init()的定义位于env.c中,在此之前,env_init由板上贴装的存储外设驱动实现。例如:

    ./common/env_nand.c:66:int env_init(void)
    ./common/env_nowhere.c:29:int env_init(void)
    ./common/env_nvram.c:92:int env_init(void)
    ./common/env_flash.c:61:int env_init(void)
    ./common/env_flash.c:210:int env_init(void)
    ./common/env_ubi.c:25:int env_init(void)
    ./common/board_f.c:882:	env_init,
    ./common/env_remote.c:28:int env_init(void)
    ./common/env_dataflash.c:77:int env_init(void)
    ./common/env_fat.c:28:int env_init(void)
    ./common/env_onenand.c:109:int env_init(void)
    ./common/spl/spl_net.c:21:	env_init();
    ./common/env_sf.c:356:int env_init(void)
    Binary file ./common/env_mmc.o matches
    ./common/env_sata.c:56:int env_init(void)
    ./common/env_eeprom.c:247:int env_init(void)
    ./common/env_mmc.su:4:env_mmc.c:62:5:env_init	0	static
    ./common/env_mmc.c:62:int env_init(void)
    ubuntu16@localhost:uboot-2016$ 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    对于传统的这种env_init(),环境变量的初始化值由default_environment[]决定,默认包含的环境变量如bootargs、bootcmd、ipaddr等等,均可以通过CONFIG_XXX来实现。

    对于最新的u-boot工程,env_init()首先会检查存储外设驱动中是否实现了相应的loadsaveinit等函数。若未实现,采用默认的default_environment[]

    	gd->env_addr	= (ulong)&default_environment[0];
    	gd->env_valid	= 1;
    
    • 1
    • 2
    1.1.2 initr_env

    它位于board_r.c中,意味着第二次对环境变量进行初始化。若需要从存储介质中加载环境变量,例如使用了saveenv更改环境变量后重启,则从存储介质中读取环境变量,否则,继续使用默认的环境变量列表。

    static int initr_env(void)
    {
    	/* initialize environment */
    	if (should_load_env())
    		env_relocate();
    	else
    		set_default_env(NULL);
    #ifdef CONFIG_OF_CONTROL
    	setenv_addr("fdtcontroladdr", gd->fdt_blob);
    #endif
    
    	/* Initialize from environment */
    	load_addr = getenv_ulong("loadaddr", 16, load_addr);
    #if defined(CONFIG_SYS_EXTBDINFO)
    #if defined(CONFIG_405GP) || defined(CONFIG_405EP)
    #if defined(CONFIG_I2CFAST)
    	/*
    	 * set bi_iic_fast for linux taking environment variable
    	 * "i2cfast" into account
    	 */
    	{
    		char *s = getenv("i2cfast");
    
    		if (s && ((*s == 'y') || (*s == 'Y'))) {
    			gd->bd->bi_iic_fast[0] = 1;
    			gd->bd->bi_iic_fast[1] = 1;
    		}
    	}
    #endif /* CONFIG_I2CFAST */
    #endif /* CONFIG_405GP, CONFIG_405EP */
    #endif /* CONFIG_SYS_EXTBDINFO */
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    1.2 修改

    待更改的环境变量时通过hash进行管理,更改并不会保存到存储设备中。u-boot中的hash代码位于lib/hashtable.c中,它是非常好的一个哈希实现。

    修改环境变量

    1.3 保存

    保存环境变量的代码由两部分组成,其一是环境变量处理函数do_save_env(),其二是由存储设备驱动实现的saveenv()

    保存环境变量

    例如,环境变量存储于mmc时,需要mmc驱动支持,流程如下:

    保存环境变量中驱动的支持

    1.4 重置

    当改动环境变量、完成调试之后,可将环境变量恢复到原来的默认值,需要注意的是执行了env default命令后,执行env save才可以真正的将环境变量恢复到默认值。

    => env default -f -a
    ## Resetting to default environment
    
    • 1
    • 2

    恢复的默认值从default_environment[]中获取。

    void set_default_env(const char *s)
    {
    	int flags = 0;
    
    	if (sizeof(default_environment) > ENV_SIZE) {
    		puts("*** Error - default environment is too large\n\n");
    		return;
    	}
    
    	if (s) {
    		if (*s == '!') {
    			printf("*** Warning - %s, "
    				"using default environment\n\n",
    				s + 1);
    		} else {
    			flags = H_INTERACTIVE;
    			puts(s);
    		}
    	} else {
    		puts("Using default environment\n\n");
    	}
    
    	if (himport_r(&env_htab, (char *)default_environment,
    			sizeof(default_environment), '\0', flags, 0,
    			0, NULL) == 0)
    		error("Environment import failed: errno = %d\n", errno);
    
    	gd->flags |= GD_FLG_ENV_READY;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    2. bootargs

    2.1 初始化

    在U-Boot中命令行参数用bootargs表示,若希望bootargs生效,一定要打开CONFIG_BOOTARGS配置选项。

    赋值方式有两种,其一是通过宏定义的方式进行赋值,该赋值针对的是默认环境变量而言,是对default_environment[]中的bootargs进行初始化,也就是说,它指定的是默认bootargs选项。

    const uchar default_environment[] = {
    #endif
    #ifdef	CONFIG_ENV_CALLBACK_LIST_DEFAULT
    	ENV_CALLBACK_VAR "=" CONFIG_ENV_CALLBACK_LIST_DEFAULT "\0"
    #endif
    #ifdef	CONFIG_ENV_FLAGS_LIST_DEFAULT
    	ENV_FLAGS_VAR "=" CONFIG_ENV_FLAGS_LIST_DEFAULT "\0"
    #endif
    #ifdef	CONFIG_BOOTARGS
    	"bootargs="	CONFIG_BOOTARGS			"\0"
    #endif
    #ifdef	CONFIG_BOOTCOMMAND
    	"bootcmd="	CONFIG_BOOTCOMMAND		"\0"
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    那么,也可以在BOOTCMD中直接对bootargs进行赋值,该种赋值方式是对默认环境变量bootargs的一种补充。

    altbootcmd=run ${subbootcmds}
    bootcmd=run ${subbootcmds}
    configure=run set_uimage; setenv tftppath ${IVM_Symbol} ; km_setboardid && saveenv && reset
    subbootcmds=tftpfdt tftpkernel nfsargs add_default boot
    nfsargs=setenv bootargs root=/dev/nfs rw nfsroot=${serverip}:${toolchain}/${arch}
    tftpfdt=if run set_fdthigh || test ${arch} != arm; then if tftpboot ${fdt_addr_r} ${tftppath}/fdt_0x${IVM_BoardId}_0x${IVM_HWKey}.dtb; then; else tftpboot ${fdt_addr_r} ${tftppath}/${hostname}.dtb; fi; else true; fi
    tftpkernel=tftpboot ${load_addr_r} ${tftppath}/${uimage}
    toolchain=/opt/eldk
    rootfssize=0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.3 修改

    目前,U-Boot传递给kernel的命令行参数位于内核设备树中的chosen结点中,因此,在修改U-Boot传递给kernel的命令行参数时,只需要考虑修改bootargs,由U-Boot的fdt_chosen()函数获取到最新的bootargs

    当U-Boot启动加载kernel时,由CONFIG_BOOTCMD指定的bootzbootm,均会调用do_bootm_linux(),在该流程中完成bootargs的最后调整,通过getenv()函数获取新的bootargs

    /* cmd/nvedit.c */
    char *getenv(const char *name)
    {
    	if (gd->flags & GD_FLG_ENV_READY) { /* after import into hashtable */
    		ENTRY e, *ep;
    
    		WATCHDOG_RESET();
    
    		e.key	= name;
    		e.data	= NULL;
    		hsearch_r(e, FIND, &ep, &env_htab, 0);
    
    		return ep ? ep->data : NULL;
    	}
    
    	/* restricted capabilities before import */
    	if (getenv_f(name, (char *)(gd->env_buf), sizeof(gd->env_buf)) > 0)
    		return (char *)(gd->env_buf);
    
    	return NULL;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    综上,U-Boot中对bootargs的处理流程如下图所示:

    image-20220903185206150

    二、kernel下的命令行处理

    文中所述基于kernel 5.4,ARM 平台

    kernel对U-Boot传递过来的命令行参数的处理如上图所示,详细内容见下文。

    image-20220903185113269

    1. 命令行参数地址

    若使用U-Boot作为加载kernel的bootloader,它给kernel传递参数的方式有两种,一种是原始的atags,其直接存放命令行参数;另外一种是现阶段广泛使用的fdt,将命令行参数存放于fdt的chosen结点中,当我们获取到fdt地址后,再解析fdt文件中的chosen结点即可获取到命令行参数。

    u-boot将处理器的控制权交付给kernel之前,会对处理器的通用寄存器进行赋值,填充机器码以及参数地址信息,kernel启动后,在汇编阶段获取到此部分信息,然后基于此进行后续一系列的解析和应用。

    获取dtb地址的代码位于kernel的汇编文件中,代码位于head-common.S

    	__INIT
    __mmap_switched:
    ......
    	.long	processor_id			@ r0
    	.long	__machine_arch_type		@ r1
    	.long	__atags_pointer			@ r2
    ......
    	b	start_kernel
    ENDPROC(__mmap_switched)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    将r2寄存器中存放的dtb地址赋值给__atags_pointer后,跳转到start_kernel中执行C语言环境的初始化工作。

    2 命令行参数获取

    在进入到start_kernel()函数后,同command_line相关的函数有两个,分别是setup_arch(&command_line)setup_command_line(command_line),前者完成命令行的解析,后者针对命令行的应用。

    /* init/main.c */
    asmlinkage __visible void __init start_kernel(void)
    {
    	char *command_line;
    	char *after_dashes;
    	......
    	setup_arch(&command_line);
    	setup_command_line(command_line);
    	......
    }	
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    该函数利用到了汇编代码中从R2寄存器获取到的__atags_pointer,将该值作为设备树地址,并再次完成设备树正确性检查,并获取到boot_command_line字符串。

    /* arch/arm/kernel/setup.c */
    void __init setup_arch(char **cmdline_p)
    {
    	const struct machine_desc *mdesc = NULL;
    	void *atags_vaddr = NULL;
    
    	if (__atags_pointer)
    		atags_vaddr = FDT_VIRT_BASE(__atags_pointer);
    
    	setup_processor();
    	if (atags_vaddr) {
    		mdesc = setup_machine_fdt(atags_vaddr);
    		if (mdesc)
    			memblock_reserve(__atags_pointer,
    					 fdt_totalsize(atags_vaddr));
    	}
    	if (!mdesc)
    		mdesc = setup_machine_tags(atags_vaddr, __machine_arch_type);
    	if (!mdesc) {
    		early_print("\nError: invalid dtb and unrecognized/unsupported machine ID\n");
    		early_print("  r1=0x%08x, r2=0x%08x\n", __machine_arch_type,
    			    __atags_pointer);
    		if (__atags_pointer)
    			early_print("  r2[]=%*ph\n", 16, atags_vaddr);
    		dump_machine_table();
    	}
    ......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    boot_command_line的定义位于init/main.c中,它作为命令行解析后的中转变量,后续会复制给cmd_line

    /* init/main.c */
    /* Untouched command line saved by arch-specific code. */
    char __initdata boot_command_line[COMMAND_LINE_SIZE];
    
    • 1
    • 2
    • 3

    setup_arch()调用setup_machine_fdt(),进行设备树合法性检查。

    /* arch/arm/kernel/devtree.c */
    const struct machine_desc * __init setup_machine_fdt(void *dt_virt)
    {
    	const struct machine_desc *mdesc, *mdesc_best = NULL;
    ......
    	if (!dt_virt || !early_init_dt_verify(dt_virt))
    		return NULL;
    
    	mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);
    
    	if (!mdesc) {
    		const char *prop;
    		int size;
    		unsigned long dt_root;
    
    		early_print("\nError: unrecognized/unsupported "
    			    "device tree compatible list:\n[ ");
    
    		dt_root = of_get_flat_dt_root();
    		prop = of_get_flat_dt_prop(dt_root, "compatible", &size);
    		while (size > 0) {
    			early_print("'%s' ", prop);
    			size -= strlen(prop) + 1;
    			prop += strlen(prop) + 1;
    		}
    		early_print("]\n\n");
    
    		dump_machine_table(); /* does not return */
    	}
    ......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    设备树合法性检查通过后,调用early_init_dt_scan_nodes()解析设备树dtb的chosen结点,获取到boot_command_line

    /* arch/arm/kernel/devtree.c */
    const struct machine_desc * __init setup_machine_fdt(void *dt_virt)
    {
    ......
    	early_init_dt_scan_nodes();
    
    	/* Change machine number to match the mdesc we're using */
    	__machine_arch_type = mdesc->nr;
    
    	return mdesc;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    early_init_dt_scan_nodes()中解析chosen结点,获取命令行参数。该结点信息除kernel设备树文件中定义之外,U-Boot中也可以对该结点进行信息追加,增加额外的命令行参数。

    /* drivers/of/fdt.c */
    void __init early_init_dt_scan_nodes(void)
    {
    	int rc = 0;
    
    	/* Retrieve various information from the /chosen node */
    	rc = of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
    	if (!rc)
    		pr_warn("No chosen node found, continuing without\n");
    
    	/* Initialize {size,address}-cells info */
    	of_scan_flat_dt(early_init_dt_scan_root, NULL);
    
    	/* Setup memory, calling early_init_dt_add_memory_arch */
    	of_scan_flat_dt(early_init_dt_scan_memory, NULL);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    3 命令函参数转出

    setup_arch()函数与处理器架构强相关,不同架构的处理器实现不同。在ARM平台下,其代码实现位于arch/arm/kernel/setup.c

    中,可以关注下其函数入参char **cmdline_p,该种方式保证了命令行参数可以在后续函数中继续使用及同步修改。

    2.1节中获取到boot_command_line字符串后,将其赋值给cmdline_p,供setup_command_line(command_line)使用。

    /* arch/arm/kernel/setup.c */
    void __init setup_arch(char **cmdline_p)
    {	
    	/* populate cmd_line too for later use, preserving boot_command_line */
    	strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
    	*cmdline_p = cmd_line;
        ......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    4 命令行参数解析

    setup_arch()函数中调用parse_early_param()完成命令行参数的解析,也就是对命令行中所定义的功能进行拆解,这部分属于kernel层面的功能,同硬件架构无关。

    /* init/main.c */
    void __init parse_early_param(void)
    {
    ......
    	parse_early_options(tmp_cmdline);
    ......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    命令行参数的解析主要获取两部分信息,其一是console相关,其二是initcall相关。

    /* init/main.c */
    static int __init do_early_param(char *param, char *val,
    				 const char *unused, void *arg)
    {
    	const struct obs_kernel_param *p;
    
    	for (p = __setup_start; p < __setup_end; p++) {
    		if ((p->early && parameq(param, p->str)) ||
    		    (strcmp(param, "console") == 0 &&
    		     strcmp(p->str, "earlycon") == 0)
    		) {
    			if (p->setup_func(val) != 0)
    				pr_warn("Malformed early option '%s'\n", param);
    		}
    	}
    	/* We accept everything at this stage. */
    	return 0;
    }
    
    void __init parse_early_options(char *cmdline)
    {
    	parse_args("early options", cmdline, NULL, 0, 0, 0, NULL,
    		   do_early_param);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    以上解析命令行参数中所涉及到的函数parse_args(),属于标准的命令行参数解析代码,在后续命令行参数应用时,除了调用该函数之外,还可以调用其他函数获取到U-Boot传递给kernel驱动的命令行参数。

    5 命令行参数应用

    命令行参数除了给kernel使用之外(如第4节所描述的那样,提供串口信息以及initcall函数),还有两种应用,分别是供proc文件系统查询命令行参数以及驱动代码获取控制参数。

    5.1 proc文件系统获取命令行参数

    我们都知道,Linux系统启动后,通过查询/proc下的命令行文件可以获取到当前系统的命令行参数,如下所示:

    ubuntu16@ubuntu16:linux-5.4$ cat /proc/cmdline 
    BOOT_IMAGE=/boot/vmlinuz-4.15.0-117-generic root=UUID=7bf3a312-30c8-4617-a187-561d457ed5b1 ro quiet splash
    ubuntu16@ubuntu16:linux-5.4$ 
    
    • 1
    • 2
    • 3

    那么,如上信息从哪里来的呢?

    在前文第2节中有提到"setup_command_line(command_line);",当kernel获取到命令行参数后会将其转出,其目的就是供该函数使用,得到saved_command_line以及static_command_line

    /* init/main.c */
    static void __init setup_command_line(char *command_line)
    {
    	size_t len = strlen(boot_command_line) + 1;
    
    	saved_command_line = memblock_alloc(len, SMP_CACHE_BYTES);
    	if (!saved_command_line)
    		panic("%s: Failed to allocate %zu bytes\n", __func__, len);
    
    	initcall_command_line =	memblock_alloc(len, SMP_CACHE_BYTES);
    	if (!initcall_command_line)
    		panic("%s: Failed to allocate %zu bytes\n", __func__, len);
    
    	static_command_line = memblock_alloc(len, SMP_CACHE_BYTES);
    	if (!static_command_line)
    		panic("%s: Failed to allocate %zu bytes\n", __func__, len);
    
    	strcpy(saved_command_line, boot_command_line);
    	strcpy(static_command_line, command_line);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    上面代码中的saved_command_line变量,会提供给proc文件系统使用,如下所示:

    /* fs/proc/cmdline.c */
    static int cmdline_proc_show(struct seq_file *m, void *v)
    {
    	seq_puts(m, saved_command_line);
    	seq_putc(m, '\n');
    	return 0;
    }
    
    static int __init proc_cmdline_init(void)
    {
    	proc_create_single("cmdline", 0, NULL, cmdline_proc_show);
    	return 0;
    }
    fs_initcall(proc_cmdline_init);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    5.2 驱动中使用命令行参数

    在驱动代码中获取命令行参数的函数是#define __setup_param(str, unique_id, fn, early),它有两种常见的封装,分别是:

    __setupearly_param。在驱动代码中随处可见这两种分装的应用。

    # __setup
    ./drivers/video/console/vgacon.c:137:__setup("no-scroll", no_scroll);
    ./drivers/pcmcia/vrc4171_card.c:694:__setup("vrc4171_card=", vrc4171_card_setup);
    ./drivers/clk/imx/clk.c:144:__setup_param("earlycon", imx_keep_uart_earlycon,
    ./drivers/clk/clk.c:1276:__setup("clk_ignore_unused", clk_ignore_unused_setup);
    ./drivers/cpuidle/sysfs.c:27:__setup("cpuidle_sysfs_switch", cpuidle_sysfs_setup);
    ./drivers/base/power/domain.c:900:__setup("pd_ignore_unused", pd_ignore_unused_setup);
    ./drivers/base/dd.c:236:__setup("deferred_probe_timeout=", deferred_probe_timeout_setup);
    ./drivers/base/devtmpfs.c:57:__setup("devtmpfs.mount=", mount_param);
    
    # early_param
    ./drivers/char/random.c:861:early_param("random.trust_cpu", parse_trust_cpu);
    ./drivers/iommu/irq_remapping.c:81:early_param("intremap", setup_irqremap);
    ./drivers/acpi/tables.c:871:early_param("acpi_apic_instance", acpi_parse_apic_instance);
    ./drivers/acpi/osl.c:178:early_param("acpi_rsdp", setup_acpi_rsdp);
    ./drivers/irqchip/irq-ls-scfg-msi.c:83:early_param("lsmsi", early_parse_ls_scfg_msi);
    ./drivers/cpufreq/intel_pstate.c:2853:early_param("intel_pstate", intel_pstate_setup);
    ./drivers/pci/pci.c:6483:early_param("pci", pci_setup);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    当我们希望在自己的驱动代码中增加命令行解析功能时,按照如下形式进行定义即可:

    /* __setup */
    static int __init setup_bert_disable(char *str)
    {
    	bert_disable = 1;
    
    	return 0;
    }
    __setup("bert_disable", setup_bert_disable);
    
    /* early_param */
    static int __init sysfs_deprecated_setup(char *arg)
    {
    	return kstrtol(arg, 10, &sysfs_deprecated);
    }
    early_param("sysfs.deprecated", sysfs_deprecated_setup);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
  • 相关阅读:
    Java 端口扫描器示例
    mitmproxy 使用
    经典算法之快速排序
    深入浅出 OkHttp 源码解析及应用实践
    设0<c<1,a1=c/2,a(n+1)=c/2+an²/2,证明数列an收敛,并求其极限
    8-汇编-寄存器(CPU工作原理)03
    大一学生期末大作业 html+css+javascript网页设计实例【电影购票项目】html网页制作成品代码
    女朋友让我深夜十二点催她睡觉,我有Python我就不干
    (AtCoder Beginner Contest 321)(退背包,二叉树)
    常规的知识积累
  • 原文地址:https://blog.csdn.net/weixin_43644245/article/details/126676638