Linux内核启动流程 详解

2023-11-08

Linux内核启动流程 

  arch/arm/kernel/head-armv.S 

  该文件是内核最先执行的一个文件,包括内核入口ENTRY(stext)到start_kernel间的初始化代码,

  主要作用是检查CPU ID, Architecture Type,初始化BSS等操作,并跳到start_kernel函数。在执行前,处理器应满足以下状态: 

r0 - should be 0 
r1 - unique architecture number 
MMU - off 
I-cache - on or off 
D-cache – off 

复制代码

 1 /* 部分源代码分析 */ 
 2 /* 内核入口点 */ 
 3 ENTRY(stext) 
 4 /* 程序状态,禁止FIQ、IRQ,设定SVC模式 */ 
 5 mov r0, #F_BIT | I_BIT | MODE_SVC@ make sure svc mode 
 6 /* 置当前程序状态寄存器 */ 
 7 msr cpsr_c, r0 @ and all irqs disabled 
 8 /* 判断CPU类型,查找运行的CPU ID值与Linux编译支持的ID值是否支持 */ 
 9 bl __lookup_processor_type 
10 /* 跳到__error */ 
11 teq r10, #0 @ invalid processor? 
12 moveq r0, #'p' @ yes, error 'p' 
13 beq __error 
14 /* 判断体系类型,查看R1寄存器的Architecture Type值是否支持 */ 
15 bl __lookup_architecture_type 
16 /* 不支持,跳到出错 */ 
17 teq r7, #0 @ invalid architecture? 
18 moveq r0, #'a' @ yes, error 'a' 
19 beq __error 
20 /* 创建核心页表 */ 
21 bl __create_page_tables 
22 adr lr, __ret @ return address 
23 add pc, r10, #12 @ initialise processor 
24 /* 跳转到start_kernel函数 */ 
25 b start_kernel 

复制代码

1. start_kernel()函数分析

  下面对start_kernel()函数及其相关函数进行分析。 

1.1 lock_kernel() 

复制代码

 1 /* Getting the big kernel lock. 
 2 * This cannot happen asynchronously, 
 3 * so we only need to worry about other 
 4 * CPU's. 
 5 */ 
 6 extern __inline__ void lock_kernel(void) 
 7 { 
 8     if (!++current->lock_depth) 
 9     spin_lock(&kernel_flag); 
10 } 

复制代码

  kernel_flag 是一个内核大自旋锁,所有进程都通过这个大锁来实现向内核态的迁移。

  只有获得这个大自旋锁的处理器可以进入内核,如中断处理程序等。在任何一对 lock_kernel/unlock_kernel函数里至多可以有一个程序占用CPU。

  进程的lock_depth成员初始化为-1,在 kerenl/fork.c文件中设置。在它小于0时 (恒为 -1),进程不拥有内核锁;当大于或等于0时,进程得到内核锁。 

1.2 setup_arch() 

  setup_arch()函数做体系相关的初始化工作,函数的定义在arch/arm/kernel/setup.c文件中,主要涉及下列主要函数及代码。 

  setup_processor() 
  该函数主要通过 

for (list = &__proc_info_begin; list < &__proc_info_end ; list++) 
if ((processor_id & list->cpu_mask) == list->cpu_val) 
break; 

  这样一个循环来在.proc.info段中寻找匹配的processor_id,processor_id在head_armv.S文件中设置。 

1.2.2 setup_architecture(machine_arch_type) 

  该函数获得体系结构的信息,返回mach-xxx/arch.c 文件中定义的machine结构体的指针,包含以下内容 

复制代码

MACHINE_START (xxx, “xxx”) 
MAINTAINER ("xxx" 
BOOT_MEM (xxx, xxx, xxx) 
FIXUP (xxx) 
MAPIO (xxx) 
INITIRQ (xxx) 
MACHINE_END 

复制代码

1.2.3内存设置代码 

复制代码

if (meminfo.nr_banks == 0) 
{ 
    meminfo.nr_banks = 1; 
    meminfo.bank[0].start = PHYS_OFFSET; 
    meminfo.bank[0].size = MEM_SIZE; 
} 

复制代码

 

  meminfo结构表明内存情况,是对物理内存结构meminfo的默认初始化。

  nr_banks指定内存块的数量,bank指定每块内存的范围,PHYS _OFFSET指定某块内存块的开始地址,MEM_SIZE指定某块内存块长度。 PHYS _OFFSET和MEM_SIZE都定义在include/asm-armnommu/arch-XXX/memory.h文件中,其中 PHYS _OFFSET是内存的开始地址,MEM_SIZE就是内存的结束地址。

  这个结构在接下来内存的初始化代码中起重要作用。 

1.2.4 内核内存空间管理 

init_mm.start_code = (unsigned long) &_text; 内核代码段开始 
init_mm.end_code = (unsigned long) &_etext; 内核代码段结束 
init_mm.end_data = (unsigned long) &_edata; 内核数据段开始 
init_mm.brk = (unsigned long) &_end; 内核数据段结束 

 

  每一个任务都有一个mm_struct结构管理其内存空间,init_mm 是内核的mm_struct。

  其中设置成员变量* mmap指向自己, 意味着内核只有一个内存管理结构,设置 pgd=swapper_pg_dir,swapper_pg_dir是内核的页目录,ARM体系结构的内核页目录大小定义为16k。init_mm定义了整个内核的内存空间,内核线程属于内核代码,同样使用内核空间,其访问内存空间的权限与内核一样。 

1.2.5 内存结构初始化

  bootmem_init (&meminfo)函数根据meminfo进行内存结构初始化。

  bootmem_init(&meminfo)函数中调用reserve_node_zero(bootmap_pfn, bootmap_pages) 函数,这个函数的作用是保留一部分内存使之不能被动态分配。

  这些内存块包括:

reserve_bootmem_node(pgdat, __pa(&_stext), &_end - &_stext); /*内核所占用地址空间*/ 
reserve_bootmem_node(pgdat, bootmap_pfn<<PAGE_SHIFT, bootmap_pages<<PAGE_SHIFT) /*bootmem结构所占用地址空间*/

1.2.6 paging_init(&meminfo, mdesc) 

  创建内核页表,映射所有物理内存和IO空间,对于不同的处理器,该函数差别比较大。

  下面简单描述一下ARM体系结构的存储系统及MMU相关的概念。
  在ARM存储系统中,使用内存管理单元(MMU)实现虚拟地址到实际物理地址的映射。

  利用MMU,可把SDRAM的地址完全映射到0x0起始的一片连续地址空间,而把原来占据这片空间的FLASH或者ROM映射到其他不相冲突的存储空间位置。

  例如,FLASH的地址从0x0000 0000~0x00FFFFFF,而SDRAM的地址范围是0x3000 0000~0x3lFFFFFF,则可把SDRAM地址映射为0x0000 0000~0xlFFFFFF,而FLASH的地址可以映射到0x9000 0000~0x90FFFFFF(此处地址空间为空闲,未被占用)。映射完成后,如果处理器发生异常,假设依然为IRQ中断,PC指针指向0xl8处的地址,而这个时候PC实际上是从位于物理地址的0x3000 0018处读取指令。

  通过MMU的映射,则可实现程序完全运行在SDRAM之中。在实际的应用中.可能会把两片不连续的物理地址空间分配给SDRAM。而在操作系统中,习惯于把SDRAM的空间连续起来,方便内存管理,且应用程序申请大块的内存时,操作系统内核也可方便地分配。通过MMU可实现不连续的物理地址空间映射为连续的虚拟地址空间。操作系统内核或者一些比较关键的代码,一般是不希望被用户应用程序访问。通过MMU可以控制地址空间的访问权限,从而保护这些代码不被破坏。 

  MMU的实现过程,实际上就是一个查表映射的过程。建立页表是实现MMU功能不可缺少的一步。页表位于系统的内存中,页表的每一项对应于一个虚拟地址到物理地址的映射。每一项的长度即是一个字的长度(在ARM中,一个字的长度被定义为4Bytes)。页表项除完成虚拟地址到物理地址的映射功能之外,还定义了访问权限和缓冲特性等。
  MMU的映射分为两种,一级页表的变换和二级页表变换。两者的不同之处就是实现的变换地址空间大小不同。
  一级页表变换支持1 M大小的存储空间的映射,而二级可以支持64 kB,4 kB和1 kB大小地址空间的映射。

动态表(页表)的大小=表项数*每个表项所需的位数,即为整个内存空间建立索引表时,需要多大空间存放索引表本身。 
表项数=虚拟地址空间/每页大小 
每个表项所需的位数=Log(实际页表数)+适当控制位数 
实际页表数 =物理地址空间/每页大小

1.3 parse_options() 

  分析由内核引导程序发送给内核的启动选项,在初始化过程中按照某些选项运行,并将剩余部分传送给init进程。

  这些选项可能已经存储在配置文件中,也可能是由用户在系统启动时敲入的。但内核并不关心这些,这些细节都是内核引导程序关注的内容,嵌入式系统更是如此。 

1.4 trap_init() (/kernel/traps.c do_trap)

  这个函数用来做体系相关的中断处理的初始化,在该函数中调用__trap_init((void *)vectors_base()) 

  函数将exception vector设置到vectors_base开始的地址上。 __trap_init函数位于entry-armv.S文件中,对于ARM处理器,共有复位、未定义指令、SWI、预取终止、数据终止、IRQ和FIQ 几种方式。

  SWI主要用来实现系统调用,而产生了IRQ之后,通过exception vector进入中断处理过程,执行do_IRQ函数。

  armnommu的trap_init()函数在arch/armnommu/kernel/traps.c文件中。

  vectors_base是写中断向量的开始地址,在include/asm-armnommu/proc-armv/system.h文件中设置,地址为0或0XFFFF0000。 

复制代码

ENTRY(__trap_init) 
stmfd sp!, {r4 - r6, lr} 

mrs r1, cpsr @ code from 2.0.38 
bic r1, r1, #MODE_MASK @ clear mode bits /* 设置svc模式,disable IRQ,FIQ */ 
orr r1, r1, #I_BIT|F_BIT|MODE_SVC @ set SVC mode, disable IRQ,FIQ 
msr cpsr, r1 

adr r1, .LCvectors @ set up the vectors 
ldmia r1, {r1, r2, r3, r4, r5, r6, ip, lr} 
stmia r0, {r1, r2, r3, r4, r5, r6, ip, lr} /* 拷贝异常向量 */ 

add r2, r0, #0x200 
adr r0, __stubs_start @ copy stubs to 0x200 
adr r1, __stubs_end 
1: ldr r3, [r0], #4 
str r3, [r2], #4 
cmp r0, r1 
blt 1b 
LOADREGS(fd, sp!, {r4 - r6, pc}) 

复制代码

  __stubs_start到__stubs_end的地址中包含了异常处理的代码,因此拷贝到vectors_base+0x200的位置上。 

1.5 init_IRQ() 

复制代码

 1 void __init init_IRQ(void) 
 2 { 
 3 extern void init_dma(void); 
 4 int irq; 
 5 
 6 for (irq = 0; irq < NR_IRQS; irq++) { 
 7 irq_desc[irq].probe_ok = 0; 
 8 irq_desc[irq].valid = 0; 
 9 irq_desc[irq].noautoenable = 0; 
10 irq_desc[irq].mask_ack = dummy_mask_unmask_irq; 
11 irq_desc[irq].mask = dummy_mask_unmask_irq; 
12 irq_desc[irq].unmask = dummy_mask_unmask_irq; 
13 } 
14 CSR_WRITE(AIC_MDCR, 0x7FFFE); /* disable all interrupts */ 
15 CSR_WRITE(CAHCNF,0x0);/*Close Cache*/ 
16 CSR_WRITE(CAHCON,0x87);/*Flush Cache*/ 
17 while(CSR_READ(CAHCON)!=0); 
18 CSR_WRITE(CAHCNF,0x7);/*Open Cache*/ 
19 
20 init_arch_irq(); 
21 init_dma(); 
22 } 

复制代码

 

  这个函数用来做体系相关的irq处理的初始化.

  irq_desc数组是用来描述IRQ的请求队列,每一个中断号分配一个irq_desc结构,组成了一个数组。

  NR_IRQS代表中断数目,这里只是对中断结构irq_desc进行了初始化。

  在默认的初始化完成后调用初始化函数init_arch_irq,先执行arch/armnommu/kernel/irq-arch.c文件中的函数genarch_init_irq(),然后就执行 include/asm-armnommu/arch-xxxx/irq.h中的inline函数irq_init_irq,在这里对irq_desc进行了实质的初始化。

  其中mask用阻塞中断;unmask用来取消阻塞;mask_ack的作用是阻塞中断,同时还回应ack给硬件表示这个中断已经被处理了,否则硬件将再次发生同一个中断。这里,不是所有硬件需要这个ack回应,所以很多时候mask_ack与mask用的是同一个函数。

  接下来执行init_dma()函数,如果不支持DMA,可以设置include/asm-armnommu/arch-xxxx/dma.h中的 MAX_DMA_CHANNELS为0,这样在arch/armnommu/kernel/dma.c文件中会根据这个定义使用不同的函数。 

1.6 sched_init() 

  初始化系统调度进程,主要对定时器机制和时钟中断的Bottom Half的初始化函数进行设置。

  与时间相关的初始化过程主要有两步:

  (1)调用 init_timervecs()函数初始化内核定时器机制;

  (2)调用init_bh()函数将BH向量TIMER_BH、TQUEUE_BH和 IMMEDIATE_BH所对应的BH函数分别设置成timer_bh()、tqueue_bh()和immediate_bh()函数 

1.7 softirq_init() 

  内核的软中断机制初始化函数。

  调用tasklet_init初始化tasklet_struct结构,软中断的个数为32个。用于bh的 tasklet_struct结构调用tasklet_init()以后,它们的函数指针func全都指向bh_action()。

  bh_action就是tasklet实现bh机制的代码,但此时具体的bh函数还没有指定。

  HI_SOFTIRQ用于实现bottom half,TASKLET_SOFTIRQ用于公共的tasklet。 

open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL); /* 初始化公共的tasklet_struct要用到的软中断 */ 
open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL); /* 初始化tasklet_struct实现的bottom half调用 */ 

1.8 time_init() 

  这个函数用来做体系相关的timer的初始化,armnommu的在arch/armnommu/kernel/time.c。

  这里调用了在 include/asm-armnommu/arch-xxxx/time.h中的inline函数setup_timer。

  setup_timer()函数的设计与硬件设计紧密相关,主要是根据硬件设计情况设置时钟中断号和时钟频率等。 

复制代码

 1 void __inline__ setup_timer (void) 
 2 { 
 3 /*----- disable timer -----*/ 
 4 CSR_WRITE(TCR0, xxx); 
 5 
 6 CSR_WRITE (AIC_SCR7, xxx); /* setting priority level to high */ 
 7 /* timer 0: 100 ticks/sec */ 
 8 CSR_WRITE(TICR0, xxx); 
 9 
10 timer_irq.handler = xxxxxx_timer_interrupt; 
11 setup_arm_irq(IRQ_TIMER, &timer_irq); /* IRQ_TIMER is the interrupt number */ 
12 
13 INT_ENABLE(IRQ_TIMER); 
14 /* Clear interrupt flag */ 
15 CSR_WRITE(TISR, xxx); 
16 
17 /* enable timer */ 
18 CSR_WRITE(TCR0, xxx); 
19 } 

复制代码

1.9 console_init() 

  控制台初始化。控制台也是一种驱动程序,由于其特殊性,提前到该处完成初始化,主要是为了提前看到输出信息,据此判断内核运行情况。

  很多嵌入式Linux操作系统由于没有在/dev目录下正确配置console设备,造成启动时发生诸如unable to open an initial console的错误。 

1.10 init_modules() 

  模块初始化。如果编译内核时使能该选项,则内核支持模块化加载/卸载功能 

1.11 kmem_cache_init() 

  内核Cache初始化

1.12 sti() 

  使能中断,这里开始,中断系统开始正常工作。

1.13 calibrate_delay() 

  近似计算BogoMIPS数字的内核函数。作为第一次估算,calibrate_delay计算出在每一秒内执行多少次__delay循环,也就是每个定时器滴答(timer tick)―百分之一秒内延时循环可以执行多少次。这种计算只是一种估算,结果并不能精确到纳秒,但这个数字供内核使用已经足够精确了。 

  BogoMIPS的数字由内核计算并在系统初始化的时候打印。它近似的给出了每秒钟CPU可以执行一个短延迟循环的次数。在内核中,这个结果主要用于需要等待非常短周期的设备驱动程序――例如,等待几微秒并查看设备的某些信息是否已经可用。 

  计算一个定时器滴答内可以执行多少次循环需要在滴答开始时就开始计数,或者应该尽可能与它接近。全局变量jiffies中存储了从内核开始保持跟踪时间开始到现在已经经过的定时器滴答数, jiffies保持异步更新,在一个中断内——每秒一百次,内核暂时挂起正在处理的内容,更新变量,然后继续刚才的工作。 

1.15 kmem_cache_sizes_init() 

  内核内存管理器的初始化,也就是初始化cache和SLAB分配机制。

1.16 pgtable_cache_init() 

  页表cache初始化。

1.17 fork_init() 

  这里根据硬件的内存情况,如果计算出的max_threads数量太大,可以自行定义。

1.18 proc_caches_init(); 

  为proc文件系统创建高速缓冲 

1.19 vfs_caches_init(num_physpages); 

  为VFS创建SLAB高速缓冲

1.20 buffer_init(num_physpages); 

  初始化buffer 

1.21 page_cache_init(num_physpages); 

  页缓冲初始化 

1.22 signals_init(); 

  创建信号队列高速缓冲 

1.23 proc_root_init(); 

  在内存中创建包括根结点在内的所有节点 

1.24 check_bugs(); 

  检查与处理器相关的bug 

1.25 smp_init(); 

1.26 rest_init();

  此函数调用kernel_thread(init, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL)函数。

1.26.1 kernel_thread()函数分析 

  这里调用了arch/armnommu/kernel/process.c中的函数kernel_thread,kernel_thread函数中通过__syscall(clone) 创建新线程。

  __syscall(clone)函数参见armnommu/kernel目录下的entry- common.S文件。

1.26.2 init()完成下列功能:

  init()函数通过kernel_thread(init, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL)的回调函数执行,完成下列功能。 

  do_basic_setup()在该函数里,sock_init()函数进行网络相关的初始化,占用相当多的内存,如果所开发系统不支持网络功能,可以把该函数的执行注释掉。 

  do_initcalls()实现驱动的初始化, 这里需要与vmlinux.lds联系起来看才能明白其中奥妙。

复制代码

static void __init do_initcalls(void) 
{ 
  initcall_t *call; 

  call = &__initcall_start; 
  do { 
   (*call)(); 
   call++; 
  } while (call < &__initcall_end); 

  /* Make sure there is no pending stuff from the initcall sequence */ 
  flush_scheduled_tasks(); 
} 

复制代码

  查看 /arch/i386/vmlinux.lds,其中有一段代码

 __initcall_start = .; 
 .initcall.init : { *(.initcall.init) } 
 __initcall_end = .; 

  其含义是__initcall_start指向代码节.initcall.init的节首,而__initcall_end指向.initcall.init的节尾。

  do_initcalls所作的是系统中有关驱动部分的初始化工作,那么这些函数指针数据是怎样放到了.initcall.init节呢?

  在include/linux/init.h文件中有如下3个定义: 

1. #define __init_call   __attribute__ ((unused,__section__ (".initcall.init" )) 

__attribute__的含义就是构建一个在.initcall.init节的指向初始函数的指针。 

2. #define __initcall(fn) static initcall_t __initcall_##fn __init_call = fn 
##意思就是在可变参数使用宏定义的时候构建一个变量名称为所指向的函数的名称,并且在前面加上__initcall_ 
3. #define module_init(x) __initcall(x); 
很多驱动中都有类似module_init(usb_init)的代码,通过该宏定义逐层解释存放到.initcall.int节中。

1.26.3 init执行过程 

  在内核引导结束并启动init之后,系统就转入用户态的运行,在这之后创建的一切进程,都是在用户态进行。 

  这里先要清楚一个概念

  就是init进程虽然是从内核开始的,即在前面所讲的init/main.c中的init()函数在启动后就已经是一个核心线程,但在转到执行init程序(如 /sbin/init)之后,内核中的init()就变成了/sbin/init程序,状态也转变成了用户态,也就是说核心线程变成了一个普通的进程。

  这样一来,内核中的init函数实际上只是用户态init进程的入口,它在执行execve("/sbin/init",argv_init, envp_init)时改变成为一个普通的用户进程。这也就是exec函数的乾坤大挪移法,在exec函数调用其他程序时,当前进程被其他进程“灵魂附体”。 

  除此之外,它们的代码来源也有差别,内核中的init()函数的源代码在/init/main.c中,是内核的一部分。而/sbin/init程序的源代码是应用程序。

  init程序启动之后,要完成以下任务:检查文件系统,启动各种后台服务进程,最后为每个终端和虚拟控制台启动一个getty进程供用户登录。由于所有其它用户进程都是由init派生的,因此它又是其它一切用户进程的父进程。 

  init进程启动后,按照/etc/inittab的内容进程系统设置。很多嵌入式系统用的是BusyBox的init,它与一般所使用的init不一样,会先执行/etc/init.d/rcS而非/etc/rc.d/rc.sysinit。

 


图解ARM Linux的启动全过程

  图解ARM-Linux的启动全过程:内核自解压阶段—>内核引导阶段—>内核初始化阶段—>BusyBox初始化阶段。

 


 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux内核启动流程 详解 的相关文章

随机推荐

  • 魔兽世界怀旧服哪个服务器金价稳定,魔兽世界怀旧服 金价到底会跌到多少的分析...

    原标题 魔兽世界怀旧服 金价到底会跌到多少的分析 魔兽世界怀旧服有一个特点 大家对金价的敏感程度堪比外汇买家 在外汇交流群都没见过如此频率的价格关注与分析 在魔兽怀旧服 几乎人人都是满仓炒家 每次金价下跌一片哀嚎的景象 还真是MMORPG里
  • adb push&pull文件方法

    adb push命令 从电脑上传送文件到设备 adb pull命令 从手机传送文件到电脑 pull命令 从手机传送文件到电脑 a cmd 控制台 adb connect ip 连接设备 b adb devices查看设备连接情况 c 将设备
  • 【VS2010学习笔记】【函数学习】一(VC6.0和VS2010主函数的不同)

    问题 为什么VC6 0中主函数为main 而VS2010中为 tmain 1 Main是所有c或c 的程序执行的起点 tmain是main为了支持unicode所使用的main的别名 tmain 不过是unicode版本的的main 2 t
  • 题目 1052: [编程入门]链表合并

    已有a b两个链表 每个链表中的结点包括学号 成绩 要求把两个链表合并 按学号升序排列 输入格式 第一行 a b两个链表元素的数量N M 用空格隔开 接下来N行是a的数据 然后M行是b的数据 每行数据由学号和成绩两部分组成 输出格式 按照学
  • 相机模型-计算机视觉

    摄像机的基本成像模型 通常称为针孔模型 pinhole model 由三维空间到像平面的中心投影变换给出 如上图 a 所示 空间点Oc是投影中心 它到平面 的距离为f 空间点Xc在平面 上的投影 像 是以点Oc为端点并经过Xc的射线与平面
  • 导出七牛云的数据到本地服务器

    大概半年多以前 七牛云就失效了 一个是欠费再一个是没有绑定域名 听说是七牛云被举报了然后就必须要实名认证了 而且测试域名的时间也变得只有一个月之久 基本没什么作用了 如果绑定域名 需要该域名是备案的域名 这对于大部分自建博客的人来说基本就是
  • Node.js实现简单爬虫 讲解

    一 什么是爬虫 网络爬虫 又称为网页蜘蛛 网络机器人 在FOAF社区中间 更经常的称为网页追逐者 是一种按照一定规则 自动的抓取万维网信息的程序或者脚本 另外一些不常使用的名字还有蚂蚁 自动索引 模拟程序或者蠕虫 搜索引擎 今日头条 网易新
  • torch函数详解

    torchvision torchvision transforms Compose transforms 把几个转换组合 torch nn Conv2d CLASS torch nn Conv2d in channels out chan
  • Webpack5 教程(3)--处理图片资源

    目录 处理图片资源 1 配置 2 添加图片资源 3 使用图片资源 4 运行指令 5 输出资源情况 6 对图片资源进行优化 修改输出资源的名称和路径 1 配置 2 修改 index html 3 运行指令 自动清空上次打包资源 1 配置 2
  • 索引表

    在我们传统的印象中 索引和表是两个不同的东西 我们总是先创建表 然后 根据查询 建立相应的索引 表和索引在物理上属于不同的存储空间 例如你建立了一个好友的通讯录 你经常需要通过指定好友的姓名来查询他的 有关信息 为了提高查询的性能 假设你的
  • Linux系统简介(简单粗暴)

    Linux的诞生 哩呐科斯 Linux之父 Linus Torwalds 1991年10月 发布了0 02版 第一个公开版 内核 1994年03月 发布1 0版内核 UNIX诞生时间为1970年1月1日 这里为什么要说到UNIX呢 主要是L
  • 如何查看jar包里的源码

    java是一种静态语言 需要将代码编译为class文件才能执行 class文件不能直接查看内容 但可以通过反编译工具查看反编译代码 反编译代码与源码去掉注释后的代码比较接近 虽然比源码损失了一部分可读性 但至少有一定的可读性 工具 jd g
  • 用eclipse建立一个servlet类型的文件,配置tomcat及web.xml,并通过网页显示其结果。

    做这个的前提是你已经下载好tomcat了 可去官网下载 https tomcat apache org 步骤一 步骤二 步骤三 步骤四 步骤五 步骤六 步骤七 步骤八 步骤九 步骤十 步骤十一 步骤十二 步骤十三 步骤十四 到此就成功了 还
  • 组播测试小程序

    include
  • 【Arduino学习】03.RGB呼吸灯

    本课程中 将使用 PWM 来控制 RGB LED灯并使其显示不同的颜色 变色灯是由红 R 绿 G 蓝 B 三基色 LED 组成的 双色 LED 是我们十分熟悉的 一般由红光 LED 及绿光 LED 组成 它可以单独发出红光或绿光 若红光及绿
  • Linux服务器上设置全局代理访问外网并验证

    Linux服务器上设置全局代理访问外网并验证 昨天碰到了内网需要访问外网下载的情况 需要在服务器上设置代理 没别的 就记录一下自己跳过的坑 1 前提是已经搭建好了一台代理服务器 2 Linux设置全局代理 编辑文件 vi etc skel
  • 本周leetcode和机器学习的建模过程学习笔记

    机器学习的建模过程笔记 本周Leetlode练习 class Solution def buildArray self target List int n int gt List str res j 0 for i in range 1 t
  • 组合数学总结

    文章目录 一 组合数学基础 1 1 排列与组合 排列 组合 1 2 组合等式及其组合意义 1 3 多项式系数 二 母函数 2 1 普母函数 2 2 指母函数 2 3 正整数分拆 2 3 1 有序拆分 2 3 2 无序拆分 三 递推关系 3
  • python爬取网页时response.status_code返回418,文件读取写入

    问题 response status code为418 问题描述 当我使用Python的requests爬取网页时response和soup都是None 检查后发现response status code为418 错误描述 经过网上查询得知
  • Linux内核启动流程 详解

    Linux内核启动流程 arch arm kernel head armv S 该文件是内核最先执行的一个文件 包括内核入口ENTRY stext 到start kernel间的初始化代码 主要作用是检查CPU ID Architectur