tiny4412 串口驱动分析七 --- log打印的几个阶段之内核启动阶段(earlyprintk)
作者:彭东林
开发板:tiny4412ADK+S700 4GB Flash
主机:Wind7 64位
虚拟机:Vmware+Ubuntu12_04
u-boot:U-Boot 2010.12
Linux内核版本:linux-3.0.31
Android版本:android-4.1.2
下面要分析的是内核Log打印的几个阶段
- 自解压阶段
- 内核启动阶段
- 内核启动完全以后
- shell终端下
在这个阶段内核log打印可以调用printk和printascii,同时printk又分为两个阶段,从刚才的分析中知道,printk最终调用的是有register_console注册的console_drivers的write函数,在tiny4412平台上调用register_console的地方有两处,第一处是在arch/arm/kernel/early_printk.c中,另一处就是在串口驱动注册中,具体是在driver/tty/serial/samsung.c中,下面我们开始分析。
printascii的实现:
首先printascii需要配置内核才能使用:
make LOCALVERSION="" ARCH=arm CROSS_COMPILE=arm-linux- menuconfig
Kernel hacking
--- Kernel low-level debugging functions
这样就可以使用printascii了:
在printk中会调用printascii,
#ifdef CONFIG_DEBUG_LL
printascii(printk_buf);
#endif
print_asciii使用汇编实现的,在文件arch/arm/kernel/debug.S中:
- .macro addruart_current, rx, tmp1, tmp2
- addruart \tmp1, \tmp2
- mrc p15, , \rx, c1, c0
- tst \rx, #
- moveq \rx, \tmp1
- movne \rx, \tmp2
- .endm
- ENTRY(printascii)
- addruart_current r3, r1, r2
- b 2f
- : waituart r2, r3
- senduart r1, r3
- busyuart r2, r3
- teq r1, #'\n'
- moveq r1, #'\r'
- beq 1b
- : teq r0, #
- ldrneb r1, [r0], #
- teqne r1, #
- bne 1b
- mov pc, lr
- ENDPROC(printascii)
其中 addruart 是在文件arch/arm/mach-exynos/include/mach/debug-macro.S中实现的,waituart、senduart以及busyuart是在arch/arm/plat-samsung/include/plat/debug-macro.S中实现的,大家可以参考这两个文件理解具体实现过程。
early_printk中调用register_console
tiny4412使用的内核默认是没有开启early_printk的,即log_buf中内容只有等driver/tty/serial下的串口驱动注册完成后才能输出到串口终端,在此之前调用printk的内容都缓存到log_buf中了,如果想提前使用的话,需要使能early_printk,下面说明一下如何使能early_printk。
make LOCALVERSION="" ARCH=arm CROSS_COMPILE=arm-linux- menuconfig
Kernel hacking
-- Kernel low-level debugging functions
--Early printk
要使能early_printk首先必须使能Kernel low-level debugging functions,因为early_printk最终也是使用printascii实现的,这样arch/arm/kernel/early_printk.c就会参加编译
在early_printk.c中:
- static int __init setup_early_printk(char *buf)
- {
- printk("%s enter\n", __func__);
- register_console(&early_console);
- return ;
- }
- early_param("earlyprintk", setup_early_printk);
虽然内核配置了,但是要让内核调用setup_early_printk还必须在u-boot给内核传参的时候给bootargs在加上一个参数”earlyprintk”,如下:
- set bootargs ‘console=ttySAC0,115200n8 androidboot.console=ttySAC0 uhost0=n ctp= skipcali=y vmalloc=384m lcd=S70 earlyprintk’
那么内核是如何处理的呢?
在文件include/linux/init.h中:
- #define __setup_param(str, unique_id, fn, early) \
- static const char __setup_str_##unique_id[] __initconst \
- __aligned() = str; \
- static struct obs_kernel_param __setup_##unique_id \
- __used __section(.init.setup) \
- __attribute__((aligned((sizeof(long))))) \
- = { __setup_str_##unique_id, fn, early }
- #define early_param(str, fn) \
- __setup_param(str, fn, fn, )
将early_param("earlyprintk", setup_early_printk);展开后:
- static const char __setup_str_setup_early_printk[] __initconst __aligned() = "earlyprintk";
- static struct obs_kernel_param __setup_setup_early_printk __used __section(.init.setup) \
- __attribute__((aligned((sizeof(long))))) = \
- { __setup_str_setup_early_printk, setup_early_printk, }
即上面的这个结构体被链接到了”.init.setup”段,在arch/arm/kernel/vmlinux.lds中:
- . = ALIGN(); __setup_start = .; *(.init.setup) __setup_end = .;
将所有的.init.setup都链接到了__setup_start和__setup_end之间
在内核启动的时候:
- start_kernel
- --- setup_arch(&command_line);
- //这个函数的目的是获得u-boot传给内核的参数(bootargs),并将参数存放在command_line中,然后解析command_line,并调用相关的函数处理--- parse_early_param() (arch/arm/kernel/setup.c)
- --- strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE);
- --- parse_early_options(tmp_cmdline);
parse_early_options用于解析tmp_cmdline
- void __init parse_early_options(char *cmdline)
- {
- parse_args("early options", cmdline, NULL, , do_early_param);
- }
在文件kernel/params.c中:
- int parse_args(const char *name,
- char *args,
- const struct kernel_param *params,
- unsigned num,
- int (*unknown)(char *param, char *val))
- {
- char *param, *val;
- /* Chew leading spaces */
- args = skip_spaces(args); // 跳过args开头的空格
- while (*args) {
- int ret;
- int irq_was_disabled;
- /*
- 如:cmdline是“console=ttySAC0,115200n8 androidboot.console=ttySAC0”
- 执行next_arg后:
- params=”console”, val=” ttySAC0,115200n8” args=” androidboot.console=ttySAC0”
- */
- args = next_arg(args, ¶m, &val); // 获得下一个参数的位置,
- irq_was_disabled = irqs_disabled();
- /*
- parse_one所做的主要就是将params和val传递给unknown处理,这里unknown就是do_early_param,所以下面分析do_early_param
- */
- ret = parse_one(param, val, params, num, unknown);
- …
- }
- }
- /* All parsed OK. */
- return ;
- }
- static int __init do_early_param(char *param, char *val)
- {
- const struct obs_kernel_param *p;
- /*
- 还记得early_param("earlyprintk", setup_early_printk)展开后的结果吗?
- 其中early为1,str是 “earlyprintk”,setup_func就是setup_early_printk
- */
- for (p = __setup_start; p < __setup_end; p++) {
- if ((p->early && strcmp(param, p->str) == ) ||
- (strcmp(param, "console") == &&
- strcmp(p->str, "earlycon") == )
- ) {
- if (p->setup_func(val) != )
- …
- }
- }
- return ;
- }
下面我们就分析setup_early_printk:
- static int __init setup_early_printk(char *buf)
- {
- register_console(&early_console);
- return ;
- }
结构体early_console 的定义如下:
- static struct console early_console = {
- .name = "earlycon",
- .write = early_console_write,
- .flags = CON_PRINTBUFFER | CON_BOOT,
- .index = -,
- };
看一下early_console_write干了什么:
- static void early_console_write(struct console *con, const char *s, unsigned n)
- {
- early_write(s, n);
- }
- static void early_write(const char *s, unsigned n)
- {
- while (n-- > ) {
- if (*s == '\n')
- printch('\r');
- printch(*s);
- s++;
- }
- }
可以看到,它调用的是printch,它在文件arch/arm/kernel/debug.S中实现:
- ENTRY(printascii)
- addruart_current r3, r1, r2
- b 2f
- : waituart r2, r3
- senduart r1, r3
- busyuart r2, r3
- teq r1, #'\n'
- moveq r1, #'\r'
- beq 1b
- : teq r0, #
- ldrneb r1, [r0], #
- teqne r1, #
- bne 1b
- mov pc, lr
- ENDPROC(printascii)
- ENTRY(printch)
- addruart_current r3, r1, r2
- mov r1, r0
- mov r0, #
- b 1b
- ENDPROC(printch)
这样当driver/tty/serial下的驱动尚未注册时,printk就已经可以使用了,它最终调用的是early_console_write输出到串口终端的
下面我们分析一个函数register_console
- /*
- 这段话最好看一下
- * The console driver calls this routine during kernel initialization
- * to register the console printing procedure with printk() and to
- * print any messages that were printed by the kernel before the
- * console driver was initialized.
- *
- * This can happen pretty early during the boot process (because of
- * early_printk) - sometimes before setup_arch() completes - be careful
- * of what kernel features are used - they may not be initialised yet.
- *
- * There are two types of consoles - bootconsoles (early_printk) and
- * "real" consoles (everything which is not a bootconsole) which are
- * handled differently.
- * - Any number of bootconsoles can be registered at any time.
- * - As soon as a "real" console is registered, all bootconsoles
- * will be unregistered automatically.
- * - Once a "real" console is registered, any attempt to register a
- * bootconsoles will be rejected
- */
- void register_console(struct console *newcon)
- {
- int i;
- unsigned long flags;
- struct console *bcon = NULL;
- /*
- * before we register a new CON_BOOT console, make sure we don't
- * already have a valid console
- 这个判断的目的是:看当前系统是否有”real”类型的console注册,如果有的话,直接返回,即”real”类型和”bootconsoles”类型的console不能共存,
如果注册了”real”类型的console的话,则会对”bootconsoles”类型的console进行unregister- 如果已经有”real”类型的console,则注册”bootconsoles”类型的console时会失败,”bootconsoles”类型的console的flags设置CON_BOOT位。
- 这里在内核刚启动,还没有任何console注册,console_drivers是NULL
- */
- if (console_drivers && newcon->flags & CON_BOOT) {
- /* find the last or real console */
- for_each_console(bcon) {
- if (!(bcon->flags & CON_BOOT)) {
- printk(KERN_INFO "Too late to register bootconsole %s%d\n",
- newcon->name, newcon->index);
- return;
- }
- }
- }
- if (console_drivers && console_drivers->flags & CON_BOOT)
- bcon = console_drivers;
- // perferred_console和selected_console的初始值都是-1,但是如果在bootargs中设置了类似“console=ttySAC0,115200n8”时,在解析console参数时会设置selected_console,这个一会分析
- if (preferred_console < || bcon || !console_drivers)
- preferred_console = selected_console;
- if (newcon->early_setup) // 如果有的话,则调用
- newcon->early_setup();
- /*
- * See if we want to use this console driver. If we
- * didn't select a console we take the first one
- * that registers here.
- */
- if (preferred_console < ) {
- if (newcon->index < )
- newcon->index = ;
- if (newcon->setup == NULL ||
- newcon->setup(newcon, NULL) == ) {
- newcon->flags |= CON_ENABLED;
- if (newcon->device) {
- newcon->flags |= CON_CONSDEV;
- preferred_console = ;
- }
- }
- }
- /*
- * See if this console matches one we selected on
- * the command line.
- // console_cmdline会在解析bootargs的console参数时设置
- */
- for (i = ; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[];
- i++) {
- if (strcmp(console_cmdline[i].name, newcon->name) != )
- continue;
- if (newcon->index >= &&
- newcon->index != console_cmdline[i].index)
- continue;
- if (newcon->index < )
- newcon->index = console_cmdline[i].index;
- if (newcon->setup &&
- newcon->setup(newcon, console_cmdline[i].options) != )
- break;
- newcon->flags |= CON_ENABLED;
- newcon->index = console_cmdline[i].index;
- if (i == selected_console) { // selected_console是从bootargs中解析出来的
- newcon->flags |= CON_CONSDEV;
- preferred_console = selected_console;
- }
- break;
- }
- if (!(newcon->flags & CON_ENABLED))
- return;
- /*
- * If we have a bootconsole, and are switching to a real console,
- * don't print everything out again, since when the boot console, and
- * the real console are the same physical device, it's annoying to
- * see the beginning boot messages twice
- */
- if (bcon && ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV))
- newcon->flags &= ~CON_PRINTBUFFER;
- /*
- * Put this console in the list - keep the
- * preferred driver at the head of the list.
- */
- console_lock();
- // 这里会把跟selected_console一样的ttySAC尽量往前放
- if ((newcon->flags & CON_CONSDEV) || console_drivers == NULL) {
- newcon->next = console_drivers;
- console_drivers = newcon;
- if (newcon->next)
- newcon->next->flags &= ~CON_CONSDEV;
- } else {
- newcon->next = console_drivers->next;
- console_drivers->next = newcon;
- }
- if (newcon->flags & CON_PRINTBUFFER) {
- spin_lock_irqsave(&logbuf_lock, flags);
- con_start = log_start;
- spin_unlock_irqrestore(&logbuf_lock, flags);
- exclusive_console = newcon;
- }
- console_unlock();
- console_sysfs_notify();
- /*
- * By unregistering the bootconsoles after we enable the real console
- * we get the "console xxx enabled" message on all the consoles -
- * boot consoles, real consoles, etc - this is to ensure that end
- * users know there might be something in the kernel's log buffer that
- * went to the bootconsole (that they do not see on the real console)
- */
- if (bcon &&
- ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV) &&
- !keep_bootcon) {
- // 在命令行中可以设置参数,将keep_bootcon置1,就不会将bootconsole注销了
- /* we need to iterate through twice, to make sure we print
- * everything out, before we unregister the console(s)
- 如果使能了early_printk的话,下面的这条log会打印两次,因为一次是从real console,另一次是从boot consoles。原因是 当real已经注册时(上面更新了console_drivers),
但是此时boot consoles还尚未unregister。- */
- printk(KERN_INFO "console [%s%d] enabled, bootconsole disabled\n",
- newcon->name, newcon->index);
- for_each_console(bcon)
- if (bcon->flags & CON_BOOT)
- unregister_console(bcon); // unregister boot consoles
- } else {
- printk(KERN_INFO "%sconsole [%s%d] enabled\n",
- (newcon->flags & CON_BOOT) ? "boot" : "" ,
- newcon->name, newcon->index);
- }
- }
上面我们分析了early_printk,下面我们分析当内核启动之后的printk打印的实现。
当Linux内核启动之后,或者更确切的说是串口驱动注册后,使用的是real console,会把boot consoles都disable。
内核起来后,使用串口终端可以登录,在用户空间(对于android)是/system/bin/sh跟用户交互,它接收用户通过串口输入的命令,然后执行,它虽然在最底层都会操作串口硬件寄存器,但是跟内核的printk还不一样,前者走的是tty驱动架构,他们在底层是通过不同的机制来操作uart控制器的。下面一块分析。
在此之前,我们还要看一下u-boot给内核传参:
- console=ttySAC0,115200n8 androidboot.console=ttySAC0 uhost0=n ctp= skipcali=y vmalloc=384m lcd=S70
由于tiny4412一共有4个串口,我们使用了com0和com3,在参数中的console参数console=ttySAC0,115200n8告诉Linux,将来sh要运行在ttySAC0上,它的波特率是115200,每帧8位。也就是我们要通过com0与Linux交互。
在此可以做一个实验,如果我们执行
“hello world”会通过串口ttySAC0打印到终端
但是执行
什么反应也没有,因为我们接在com0上而不是com3上。后面我们会尝试修改默认的串口,从com0改成com3.
那么系统是如何解析console=ttySAC0,115200n8的呢?
在kernel/printk.c中:
- __setup("console=", console_setup);
在include/linux/init.h中:
- #define __setup(str, fn) \
- __setup_param(str, fn, fn, )
而__setup_param我们在上面已经分析了,最终__setup("console=", console_setup);
的展开结果是:
- static const char __setup_str_console_setup[] __initconst \
- __aligned() = "console=";
- static struct obs_kernel_param __setup_console_setup \
- __used __section(.init.setup) \
- __attribute__((aligned((sizeof(long))))) \
- = { __setup_str_console_setup, console_setup, }
可以看到它也链接到了”.init.setup”段。在内核启动的时候:
- asmlinkage void __init start_kernel(void)
- {
- char * command_line;
- extern const struct kernel_param __start___param[], __stop___param[];
- ...
- parse_args("Booting kernel", static_command_line, __start___param,
- __stop___param - __start___param,
- &unknown_bootoption);
- ......
- }
- int parse_args(const char *name,
- char *args,
- const struct kernel_param *params,
- unsigned num,
- int (*unknown)(char *param, char *val))
- {
- char *param, *val;
- DEBUGP("Parsing ARGS: %s\n", args);
- /* Chew leading spaces */
- args = skip_spaces(args);
- while (*args) {
- int ret;
- int irq_was_disabled;
- args = next_arg(args, ¶m, &val);
- …
- ret = parse_one(param, val, params, num, unknown);
- // 在parse_one中会调用unknown函数,并将param和val传给它
- …
- }
- static int __init unknown_bootoption(char *param, char *val)
- {
- /* Change NUL term back to "=", to make "param" the whole string. */
- if (val) {
- /* param=val or param="val"? */
- if (val == param+strlen(param)+)
- val[-] = '=';
- else if (val == param+strlen(param)+) {
- val[-] = '=';
- memmove(val-, val, strlen(val)+);
- val--;
- } else
- BUG();
- }
- /* Handle obsolete-style parameters */
- if (obsolete_checksetup(param))
- return ;
- /* Unused module parameter. */
- if (strchr(param, '.') && (!val || strchr(param, '.') < val))
- return ;
- if (panic_later)
- return ;
- if (val) {
- /* Environment option */
- unsigned int i;
- for (i = ; envp_init[i]; i++) {
- if (i == MAX_INIT_ENVS) {
- panic_later = "Too many boot env vars at `%s'";
- panic_param = param;
- }
- if (!strncmp(param, envp_init[i], val - param))
- break;
- }
- envp_init[i] = param;
- } else {
- /* Command line option */
- unsigned int i;
- for (i = ; argv_init[i]; i++) {
- if (i == MAX_INIT_ARGS) {
- panic_later = "Too many boot init vars at `%s'";
- panic_param = param;
- }
- }
- argv_init[i] = param;
- }
- return ;
- }
对于“console=ttySAC0,115200n8”符合这个条件
- static int __init obsolete_checksetup(char *line)
- {
- const struct obs_kernel_param *p;
- int had_early_param = ;
- p = __setup_start;
- do {
- int n = strlen(p->str);
- if (!strncmp(line, p->str, n)) {
- if (p->early) {
- /* Already done in parse_early_param?
- * (Needs exact match on param part).
- * Keep iterating, as we can have early
- * params and __setups of same names 8( */
- if (line[n] == '\0' || line[n] == '=')
- had_early_param = ;
- } else if (!p->setup_func) {
- printk(KERN_WARNING "Parameter %s is obsolete,"
- " ignored\n", p->str);
- return ;
- } else if (p->setup_func(line + n))
- return ;
- }
- p++;
- } while (p < __setup_end);
- return had_early_param;
- }
这样就会调用console_setup,并把参数:ttySAC0,115200n8 传给str变量
- static int __init console_setup(char *str)
- {
- char buf[sizeof(console_cmdline[].name) + ]; /* 4 for index */
- char *s, *options, *brl_options = NULL;
- int idx;
- /*
- * Decode str into name, index, options.
- */
- if (str[] >= '' && str[] <= '') {
- strcpy(buf, "ttyS");
- strncpy(buf + , str, sizeof(buf) - );
- } else {
- strncpy(buf, str, sizeof(buf) - );
- }
- //这里将ttySAC0,115200n8分成了两个字符串” ttySAC0”和”115200n8”
- buf[sizeof(buf) - ] = ;
- if ((options = strchr(str, ',')) != NULL)
- *(options++) = ; // 此时options指向字符串”115200n8”
- //这里会从ttySAC0中将0解析出来,这个循环执行完成后,*s就是’0’
- for (s = buf; *s; s++)
- if ((*s >= '' && *s <= '') || *s == ',')
- break;
- idx = simple_strtoul(s, NULL, ); // 将字符’0’转化成10进制类型的0,然后赋值给idx
- *s = ; // 将’0’所在的位置为0,那么就将”ttySAC0”变成了”ttySAC”
- __add_preferred_console(buf, idx, options, brl_options);
- console_set_on_cmdline = ;
- return ;
- }
name=”ttySAC”, idx=0, options=”115200n8”, brl_options=NULL
- static int __add_preferred_console(char *name, int idx, char *options,
- char *brl_options)
- {
- struct console_cmdline *c;
- int i;
- // 此时console_cmdline还没有赋值,所以name[0]是空,循环不成立
- // 如果在bootargs中传了两个console参数,那么在解析第二个console参数的时候name[0]就不是空的了
- for (i = ; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[]; i++)
- if (strcmp(console_cmdline[i].name, name) == &&
- console_cmdline[i].index == idx) {
- if (!brl_options)
- selected_console = i;
- return ;
- }
- if (i == MAX_CMDLINECONSOLES)
- return -E2BIG;
- // 从这里可以看出,如果bootargs中传递了两个console参数,如console=ttySAC0,console=ttySAC3,那么最终selected_console就是3
- if (!brl_options)
- selected_console = i;
- c = &console_cmdline[i];
- strlcpy(c->name, name, sizeof(c->name)); // “ttySAC”
- c->options = options; // “115200n8”
- c->index = idx; //
- return ;
- }
这里可以想一想,如果在bootargs中传递了两个console,如”console=ttySAC0,115200n8 console=ttySAC3,115200n8”,那么:
console_cmdline[0].name = “ttySAC”
console_cmdline[0].index = 0
console_cmdline[0].options = "115200n8"
console_cmdline[1].name = “ttySAC”
console_cmdline[1].index = 3
console_cmdline[1].options = "115200n8"
selected_console = 1
对于tiny4412,有四个串口,所以会调用四次register_console,但是只有一次能成功,那一次呢?如果bootargs中console参数是ttySAC0,那么只有名为ttySAC0的串口才能成功调用register_console。具体过程,我们下面分析。是在register_console中调用newcon->setup时,因为port的mapbase没有设置而返回错误。因为这四个串口在注册时,会调用probe四次,probe的时候才会为这个port的mapbase赋值,然后才调用register_console的。
tiny4412 串口驱动分析七 --- log打印的几个阶段之内核启动阶段(earlyprintk)的更多相关文章
- tiny4412 串口驱动分析八 --- log打印的几个阶段之内核启动阶段(printk tiny4412串口驱动的注册)
作者:彭东林 邮箱:pengdonglin137@163.com 开发板:tiny4412ADK+S700 4GB Flash 主机:Wind7 64位 虚拟机:Vmware+Ubuntu12_04 ...
- tiny4412 串口驱动分析三 --- log打印的几个阶段之内核自解压
作者:彭东林 邮箱:pengdonglin137@163.com 开发板:tiny4412ADK+S700 4GB Flash 主机:Wind7 64位 虚拟机:Vmware+Ubuntu12_04 ...
- tiny4412 串口驱动分析四 --- 修改默认的串口输出
作者:彭东林 邮箱:pengdonglin137@163.com 开发板:tiny4412ADK+S700 4GB Flash 主机:Wind7 64位 虚拟机:Vmware+Ubuntu12_04 ...
- tiny4412 串口驱动分析一 --- u-boot中的串口驱动
作者:彭东林 邮箱:pengdonglin137@163.com 开发板:tiny4412ADK+S700 4GB Flash 主机:Wind7 64位 虚拟机:Vmware+Ubuntu12_04 ...
- tiny4412 串口驱动分析二 --- printk的实现
作者:彭东林 邮箱:pengdonglin137@163.com 开发板:tiny4412ADK+S700 4GB Flash 主机:Wind7 64位 虚拟机:Vmware+Ubuntu12_04 ...
- tiny4412 串口驱动分析六 --- TTY驱动架构
转载: http://www.linuxidc.com/Linux/2013-11/92639.htm 参考: http://blog.csdn.net/lamdoc/article/details/ ...
- tiny4412 串口驱动分析五 --- LDD3上TTY驱动程序源码
关于tty这部分请参考: <Linux设备驱动开发详解 第二版>第14章 Linux终端设备驱动 <精通Linux设备驱动程序开发>第6章 串行设备驱动程序 <Linux ...
- tiny4412 串口驱动分析九 --- shell终端
作者:彭东林 邮箱:pengdonglin137@163.com 开发板:tiny4412ADK+S700 4GB Flash 主机:Wind7 64位 虚拟机:Vmware+Ubuntu12_04 ...
- linux的串口驱动分析
1.串口驱动中的数据结构 • UART驱动程序结构:struct uart_driver 驱动 • UART端口结构: struct uart_port 串口 • UART相关操作函数结构: st ...
随机推荐
- HDU 2295 Radar (二分 + Dancing Links 重复覆盖模型 )
以下转自 这里 : 最小支配集问题:二分枚举最小距离,判断可行性.可行性即重复覆盖模型,DLX解之. A*的启发函数: 对当前矩阵来说,选择一个未被控制的列,很明显该列最少需要1个行来控制,所以ans ...
- 第九章 广播和本地组播(IGMP和MLD)
距离项目开启已经过去了一段时间,这段时间内自己学习的内容也算挺多的,但是也较容易遗忘,之后应该在空余的时间内多翻翻博客,更加清楚传统计算机网络的运作. 由于51要出去玩,更要好好利用好最近的时间.完成 ...
- Servlet 返回Json数据格式
其实就是把数据库中的数据查询出来拼接成一个Json数据 import dao.UserDao; import endy.User; import javax.servlet.ServletExcept ...
- hadoop2.5.2学习及实践笔记(三)—— HDFS概念及体系结构
注:文中涉及的文件路径或配置文件中属性名称是针对hadoop2.X系列,相对于之前版本,可能有改动. 附: HDFS用户指南官方介绍: http://hadoop.apache.org/docs/r2 ...
- v-if与v-show区别
在v-show中,元素是一直存在的,当v-show为false时,元素display:none只是隐藏了而已. v-if 作用:判断是否加载固定的内容,如果为真,则加载:为假时,则不加载. 用处:用在 ...
- node.js开发hello world
在你的 D 盘下面创建一个 server.js,写入以下内容 ---------------------------------------------------- var http = requi ...
- 【bzoj1179】[Apio2009]Atm Tarjan缩点+Spfa最长路
题目描述 输入 第一行包含两个整数N.M.N表示路口的个数,M表示道路条数.接下来M行,每行两个整数,这两个整数都在1到N之间,第i+1行的两个整数表示第i条道路的起点和终点的路口编号.接下来N行,每 ...
- POJ 3243 Clever Y | BSGS算法完全版
题目: 给你A,B,K 求最小的x满足Ax=B (mod K) 题解: 如果A,C互质请参考上一篇博客 将 Ax≡B(mod C) 看作是Ax+Cy=B方便叙述与处理. 我们将方程一直除去A,C的最大 ...
- 2-sat 学习笔记
一.问题描述 以你咕的模板题为例 题目描述 有\(n\)个布尔变量\(x_1\)~\(x_n\),另有\(m\)个需要满足的条件,每个条件的形式都是"\(x_i\)为true/false或\ ...
- bzoj 2618 半平面交模板+学习笔记
题目大意 给你n个凸多边形,求多边形的交的面积 分析 题意\(=\)给你一堆边,让你求半平面交的面积 做法 半平面交模板 1.定义半平面为向量的左侧 2.将所有向量的起点放到一个中心,以中心参照进行逆 ...