Linux内核启动代码分析二之开发板相关驱动程序加载分析
Linux内核启动代码分析二之开发板相关驱动程序加载分析
1 从linux开始启动的函数start_kernel开始分析,该函数位于linux-2.6.22/init/main.c
start_kernel()
--2>setup_arch(&command_line);//该函数位于arch/arm/kernel/setup.c
//在这个函数中定义了一个描述开发板的属性的结构体struct machine_desc *mdesc
struct machine_desc {
/*
* Note! The first four elements are used
* by assembler code in head-armv.S
*/
unsigned int nr; /* architecture number */
unsigned int phys_io; /* start of physical io */
unsigned int io_pg_offst; /* byte offset for io
* page tabe entry */
const char *name; /* architecture name */
unsigned long boot_params; /* tagged list */
unsigned int video_start; /* start of video RAM */
unsigned int video_end; /* end of video RAM */
unsigned int reserve_lp0 :1; /* never has lp0 */
unsigned int reserve_lp1 :1; /* never has lp1 */
unsigned int reserve_lp2 :1; /* never has lp2 */
unsigned int soft_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);
};
//这个结构体中包括机器ID,物理地址、IO地址偏移、IO映射函数、IRQ中断函数初始化
//开发板初始化函数:完成平台驱动程序初始化注册函数的调用
--3>mdesc = setup_machine(machine_arch_type);
//machine_arch_type为外部定义的一个全局变量,用来标识机器ID
--4>struct machine_desc *list = lookup_machine_type(machine_arch_type)
//用于搜索开发板机器ID,lookup_machine_type是一个汇编函数
位于linux-2.6.22/arch/arm/kernel/head-common.S
lookup_machine_type中对机器ID号码从r0寄存器复制到r1寄存器中,调用汇编函数:__lookup_machine_type
--5>__lookup_machine_type从.arch.info.init段中比较是否存在相同机器ID号码的机器描述结构体,
在使用MACHINE_START(_type,_name)宏定义一个开发板机器描述结构体时,会把这个结构体变量放到.arch.info.init段内
//宏定义和宏开如下所述:
#define MACHINE_START(_type,_name) \
static const struct machine_desc __mach_desc_##_type \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
.nr = MACH_TYPE_##_type, \
.name = _name,
#define MACHINE_END \
};
MACHINE_START(CSB337, "Cogent CSB337")
/* Maintainer: Bill Gatliff */
.phys_io = AT91_BASE_SYS,
.io_pg_offst = (AT91_VA_BASE_SYS >> 18) & 0xfffc,
.boot_params = AT91_SDRAM_BASE + 0x100,
.timer = &at91rm9200_timer,
.map_io = csb337_map_io,
.init_irq = csb337_init_irq,
.init_machine = csb337_board_init,
MACHINE_END
//宏展开如下:
MACHINE_START(CSB337, "Cogent CSB337")
static const struct machine_desc __mach_desc_CSB337 __used __attribute__((__section__(".arch.info.init"))) = {
.nr = MACH_TYPE_CSB337,
.name = Cogent CSB337,
/* Maintainer: Bill Gatliff */
.phys_io = AT91_BASE_SYS,
.io_pg_offst = (AT91_VA_BASE_SYS >> 18) & 0xfffc,
.boot_params = AT91_SDRAM_BASE + 0x100,
.timer = &at91rm9200_timer,
.map_io = csb337_map_io,
.init_irq = csb337_init_irq,
.init_machine = csb337_board_init,
};
__lookup_machine_type函数:位于arch/arm/kernel/head-common.s
__lookup_machine_type:
adr r3, 3b
ldmia r3, {r4, r5, r6}
sub r3, r3, r4 @ get offset between virt&phys
add r5, r5, r3 @ convert virt addresses to
add r6, r6, r3 @ physical address space
1: ldr r3, [r5, #MACHINFO_TYPE] @ get machine type
teq r3, r1 @ matches loader number?
beq 2f @ found
add r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desc
cmp r5, r6
blo 1b
mov r5, #0 @ unknown machine
2: mov pc, lr
这个函数中的#MACHINFO_TYPE,不是太明白,搜索源码发现它出现在一个c文件的main函数中,被main函数调用
很不理解,按理说一条宏命令应该出现在一个h文件或函数外才对。google后的结果如下,没有进一步地观察跟踪
编译过程是不是这样,不过觉得还是挺有道理的。留待以后考证。
#define DEFINE(sym, val) \
asm volatile("\n->" #sym " %0 " #val : : "i" (val))
根据上面的宏定义,进行宏展开
DEFINE(SIZEOF_MACHINE_DESC, sizeof(struct machine_desc));
asm volatile("\n->" SIZEOF_MACHINE_DESC " %0 " sizeof(struct machine_desc) : : "i" (sizeof(struct machine_desc)))
google后的结果是:
asm volatile("\n->" #sym " %0 " #val : :"i" (val))估计是 #define sym val 的意思
也就是#define SIZEOF_MACHINE_DESC sizeof(struct machine_desc)
只不过这里是动态的定义。只需要传一个sym和val进来,就可以帮你完成#define sym val的功能
那个宏定义在asm-offsets.c中,这个.c文件根本就不是用来编译运行的,只是在编译内核的时候,
用它生成一个asm-offsets.s文件,然后使用一个脚本将这个asm-offsets.s再转换为asm-offsets.h。
这个头文件遵循汇编语法,用来被汇编文件include的。
DEFINE(MACHINFO_TYPE, offsetof(struct machine_desc, nr));
#define MACHINFO_TYPE offsetof(struct machine_desc, nr)
函数调用--4>如果lookup_machine_type(nr)查找到了目标开发板机器ID,返回一个指向该机器描述结构体的指针,否则返回0
而函数调用--3>setup_machine(machine_arch_type)中的实参machine_arch_type (也就是作为实参传入函数lookup_machine_type
函数的变量nr) 是在哪里定义的呢?
在arch/arm/tools下有三个文件:gen-mach-types、mach-types、Makefile
1 阅读gen-mach-types,发现这是一个shell脚本文件,看注释是:
generate include/asm-arm/mach-types.h用于产生mach-types.h的头文件
mach-types.h里的生成的是板子相关的宏定义
2 mach-types文件中是很多块板子的配置文件,所以当为内核添加一款新的开发板时,需要修改这个文件,
参考别的板子的定义方法,添加一项配置,其中配置的最后一项number就是机器ID号码
3 mach-types.h源码中是没有这个文件的,这个文件是编译过程中动态生成的。
# machine_is_xxx CONFIG_xxxx MACH_TYPE_xxx number
csb337 MACH_CSB337 CSB337 399
mach-types.h首先根据mach-types配置文件生成#define MACH_TYPE_XXX number的宏定义机器ID
如:#define MACH_TYPE_CSB337 399
然后成如所有配置项的其他部分,如:
#ifdef CONFIG_MACH_CSB337
# ifdef machine_arch_type
# undef machine_arch_type
# define machine_arch_type __machine_arch_type
# else
# define machine_arch_type MACH_TYPE_CSB337
# endif
# define machine_is_csb337() (machine_arch_type == MACH_TYPE_CSB337)
#else
# define machine_is_csb337() (0)
#endif
在这里我们看到了machine_arch_type的踪影。。。。
解析这段宏命令:如果定义了machine_arch_type,则结束machine_arch_type原来的宏定义,
然后把machine_arch_type重新定义成__machine_arch_type;如果没有定义过machine_arch_type,
那么就把machine_arch_type定义成mach-types中配置的机器ID号码。
Makefile中把mach-types.h动态包含到工程去。
4 如果定义了machine_arch_type宏的话,machine_arch_type又被重定义为__machine_arch_type;
__machine_arch_type又是在哪里定义的呢?
在源码中搜索__machine_arch_type,在arch/arm/boot/compressed/misc.c中发现了:
unsigned int __machine_arch_type;的定义,
并在该文件下的函数:
decompress_kernel()中对__machine_arch_type进行了初始化赋值为arch_id。
其实这个arch_id是由uboot等bootloader传入内核的一个参数。
5 追踪decompress_kernel函数
decompress_kernel是使解压内核的意义,该函数应该是在内核运行之前先运行的函数,用于把内核
从uImage镜像解压到ram中去,便于内核在内存中运行。(以上都是自己的猜测:根据这个函数中打印
出的字符和内核运行初输出到终端上的字符一致)
uImage在arch/arm/boot目录下生成,那么阅读该目录下的Makefile文件。
看不太懂什么意思,不过最后一句:subdir- := bootp compressed
那么继续看这两个子目录中的Makefile
bootp目录里都是些配置链接相关的代码,看不懂,查看linux主机发现,这个目录上没有生成*.o文件,暂时跳过。
compressed目录
这个目录下有个文件vmlinux.lds.in,编译时会生成一个vmlinux.lds
阅读这个链接脚本,好多看不太明白,不过链接脚本中.text段中放在最前面的是_start,那么可以在本目录下
寻找一个含有_start的汇编文件,发现head.s含有这个start标号,可以猜测内核最先就是从这个会变文件开始
执行下去的。分析下这个head.s汇编
5.1 保存uboot传入的参数,关闭中断进入管理模式
uboot跳入到linux内核时传入三个参数:(内核偏移地址:我自己猜的)0,机器ID,参数tags指针
分别把这三个参数保存到r0,r7,r8三个寄存器。
5.2 内核代码的重定位
adr r0, LC0//把LC0表的地址加载到r0中
ldmia r0, {r1, r2, r3, r4, r5, r6, ip, sp}//把LC0表的数据分别加载到r1 r2 r3 r4 r5 r6 r12 r13中
subs r0, r0, r1
beq not_relocated //通过比较r0 r1也就是比较LC0表的加载地址和链接地址是否相同,相同就调用not_relocated
函数,就不用再进行代码重定位了,否则还需要对代码在内存进行搬移重定位到链接地址。
标号LC0是指在内存中的地址,而这个标号作为地址的内存单元中存储的LC0是指由链接脚本指定的内存地址数据
.type LC0, #object
LC0: .word LC0 @ r1 表LC0地址
.word __bss_start @ r2 BSS段开始地址:由链接脚本确定
.word _end @ r3 BSS段结束地址:由链接脚本确定
.word zreladdr @ r4 由arch/arm/mach-xx/Makefile.boot文件配置内核加载地址
.word _start @ r5 压缩内核的开始地址
.word _got_start @ r6 got段开始地址
.word _got_end @ ip got段结束地址
.word user_stack+4096 @ sp 栈指针sp
LC1: .word reloc_end - reloc_start
.size LC0, . - LC0
假如r0 与r1值不一样,需要进行重定位:
add r5, r5, r0 重定位内核地址
add r6, r6, r0 重定位got段开始地址
add ip, ip, r0 重定位got段结束地址
add r2, r2, r0
add r3, r3, r0
add sp, sp, r0
个人理解:LC0表中除LC0 zreladdr外都是相对LC0的相对位移地址,这样就能明白这6句指令了
重定位got表:
1: ldr r1, [r6, #0] @ relocate entries in the GOT
add r1, r1, r0 @ table. This fixes up the
str r1, [r6], #4 @ C references.
cmp r6, ip
blo 1b
5.3 完成重定位后
《A》 清bss段
mov r0, #0
1: str r0, [r2], #4 @ clear bss
str r0, [r2], #4
str r0, [r2], #4
str r0, [r2], #4
cmp r2, r3
blo 1b
《B》为解压缩内核设置堆栈空间
bl cache_on //打开cache
mov r1, sp @ malloc space above stack
add r2, sp, #0x10000 @ 64k max 在栈上方开辟64K堆空间,现在r2是堆的底地址。。
《C》调整内核空间与堆栈空间的地址,然后填充decompress_kernel的四个参数
/*检查堆栈空间是否和内核uImage所站的空间重合
* r4 = final kernel address 同makefile.boot配置的内核地址(2410配置为0x30008000)
* r5 = start of this image 内核镜像地址
* r2 = end of malloc space (and therefore this image)
* We basically want:
* r4 >= r2 -> OK
* r4 + image length <= r5 -> OK
*/
cmp r4, r2
bhs wont_overwrite
sub r3, sp, r5 @ > compressed kernel size
add r0, r4, r3, lsl #2 @ allow for 4x expansion
cmp r0, r5
bls wont_overwrite
//一般地r4不会比r2大,r0比r5小,个人猜测,这部分不是太明白什么意思
mov r5, r2 @ decompress after malloc space
mov r0, r5
mov r3, r7
bl decompress_kernel
。。。。。
wont_overwrite: mov r0, r4
mov r3, r7
bl decompress_kernel
b call_kernel
《D》跳入到解压缩后的内核里去运行linux操作系统
call_kernel: bl cache_clean_flush
bl cache_off
mov r0, #0 @ must be zero
mov r1, r7 @ restore architecture number
mov r2, r8 @ restore atags pointer
mov pc, r4 @ call kernel
《E》运行Linux操作系统的前奏
阅读bootp.lds连接脚本,发现连接到真正内核可执行文件的最前面的是 _stext 段。
该段在arch/arm/kernel/head.S中
分析下面这段代码
ENTRY(stext)
msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode
@ and irqs disabled //进行管理模式 关中断
mrc p15, 0, r9, c0, c0 @ get processor id //通过协处理器命令读处理器ID号
bl __lookup_processor_type @ r5=procinfo r9=cpuid
//该函数位于arch/arm/kernel/head-common.s中
movs r10, r5 @ invalid processor (r5=0)?
beq __error_p @ yes, error 'p'
//没有找到r5为0
bl __lookup_machine_type @ r5=machinfo
//该函数位于arch/arm/kernel/head-common.s中
movs r8, r5 @ invalid machine (r5=0)?
beq __error_a @ yes, error 'a'
//没有找到r5为0
bl __create_page_tables
//创建页表
ldr r13, __switch_data @ address to jump to after
@ mmu has been enabled
adr lr, __enable_mmu @ return (PIC) address
//打开MMU
add pc, r10, #PROCINFO_INITFUNC
《F》运行Linux操作系统的前奏迷云
PROCINFO_INITFUNC 是__cpu_flush成员变量在结构体proc_info_list中的便宜量,
这可以从arch/arm/kernel/asm-offset.c中的一条语句如下得知。
DEFINE(PROCINFO_INITFUNC, offsetof(struct proc_info_list, __cpu_flush));
在arch/arm/kernel/vmlinux.lds.s中有
__proc_info_begin = .;
*(.proc.info.init)
__proc_info_end = .;
这三行把所有处理器信息结构组合在一块,就像一个结构数组。
这样查找时只要找到 __proc_infor_end 的地址,很快就能找到处理器信息结构数组。
对于机器信息也是一样
__arch_info_begin = .;
*(.arch.info.init)
__arch_info_end = .;
由Makefile可知对于arm920t系列的处理器proc-arm920.S被编译进内核
在arch/arm/mm/proc-arm920.S的448行有如下代码:
第一行伪汇编可能是把下面的这段数据存放到.proc.info.init代码段中
.section ".proc.info.init", #alloc, #execinstr
.type __arm920_proc_info,#object
__arm920_proc_info:
.long 0x41009200
.long 0xff00fff0
.long PMD_TYPE_SECT | \
PMD_SECT_BUFFERABLE | \
PMD_SECT_CACHEABLE | \
PMD_BIT4 | \
PMD_SECT_AP_WRITE | \
PMD_SECT_AP_READ
.long PMD_TYPE_SECT | \
PMD_BIT4 | \
PMD_SECT_AP_WRITE | \
PMD_SECT_AP_READ
b __arm920_setup
.long cpu_arch_name
.long cpu_elf_name
.long HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB
.long cpu_arm920_name
.long arm920_processor_functions
.long v4wbi_tlb_fns
.long v4wb_user_fns
#ifndef CONFIG_CPU_DCACHE_WRITETHROUGH
.long arm920_cache_fns
#else
.long v4wt_cache_fns
#endif
.size __arm920_proc_info, . - __arm920_proc_info
结合以上代码及add pc, r10, #PROCINFO_INITFUNC 指令可知
r10存储的是proc.info.init的中某一项的起始地址,而PROCINFO_INITFUNC是__cpu_flush
在结构体proc_info_list的偏移值,而这个值对应的正是__arm920_proc_info中的第五项
b __arm920_setup ,由此可知,接下来跳转到 __arm920_setup 处运行。
.type __arm920_setup, #function
__arm920_setup:
mov r0, #0
mcr p15, 0, r0, c7, c7 @ invalidate I,D caches on v4
mcr p15, 0, r0, c7, c10, 4 @ drain write buffer on v4
#ifdef CONFIG_MMU
mcr p15, 0, r0, c8, c7 @ invalidate I,D TLBs on v4
#endif
adr r5, arm920_crval
ldmia r5, {r5, r6}
mrc p15, 0, r0, c1, c0 @ get control register v4
bic r0, r0, r5
orr r0, r0, r6
mov pc, lr
。。。。。
.type arm920_crval, #object
arm920_crval:
crval clear=0x00003f3f, mmuset=0x00003135, ucset=0x00001130
这段代码先使I/D CACHE无效写buffer TLB无效,然后加载arm920_crval值,用来设置r0,
mov pc,lr会跳转到 arch/arm/kernel/head.s中的__enable_mmu 函数中去执行。
__enable_mmu
b __turn_mmu_on
mov pc, r13 //r13中的值是__switch_data在上面出现指令 ldr r13, __switch_data
在源码中搜索 grep '__switch_data' -nR ./*
搜索结果:./arch/arm/kernel/head-common.S:14: .type __switch_data, %object
.type __switch_data, %object
__switch_data:
.long __mmap_switched
.long __data_loc @ r4
.long __data_start @ r5
.long __bss_start @ r6
.long _end @ r7
.long processor_id @ r4
.long __machine_arch_type @ r5
.long cr_alignment @ r6
.long init_thread_union + THREAD_START_SP @ sp
。。。。。。。
.type __mmap_switched, %function
__mmap_switched:
adr r3, __switch_data + 4
ldmia r3!, {r4, r5, r6, r7}
cmp r4, r5 @ Copy data segment if needed
1: cmpne r5, r6
ldrne fp, [r4], #4
strne fp, [r5], #4
bne 1b
mov fp, #0 @ Clear BSS (and zero fp)
1: cmp r6, r7
strcc fp, [r6],#4
bcc 1b
ldmia r3, {r4, r5, r6, sp}
str r9, [r4] @ Save processor ID
str r1, [r5] @ Save machine type
bic r4, r0, #CR_A @ Clear 'A' bit
stmia r6, {r0, r4} @ Save control register values
b start_kernel
分析上述代码:
mov pc ,r13
r13中存储的是__switch_data目标内存区的首地址,这个地址上存储的是
函数 __mmap_switched 的入口地址。
这个函数完成了__data_loc数据段到__data_start数据段的数据复制、
__bss_start BSS数据段的清0、设置SP指针、保存处理器ID到全局变量processor_id中、
保存开发板机器架构类型到全局变量__machine_arch_type中、r0,r4压入栈、
最后调用start_kernel,这个函数在init/main.c中开始正式运行操作系统。
总结:
1 内核的启动过程:
a uboot传入参数
b 跳入到内核的起始地址;内核=head.o+misc.o+压缩内核镜像
c 保存uboot传入的参数,然后检查、重定位代码
d 打开cache,调整内核镜像地址、堆栈地址等为运行解压缩内核函数做准备
e 调用 decompress_kernel 函数
--> 调用平台相关的接口函数 arch_decomp_setup();
--> 调用 makecrc();
--> 调用 gunzip();
f 调用汇编函数b call_kernel,call_kernel中刷新cache,关cache,然后把
uboot传入的三个参数再传入到解压缩后的内核中。
g 通过指令 mov pc,r4,跳转到内核入口
r4中保存的是解压缩内核的入口地址,该地址为arch/arm/kernel/head.s中的
.type stext, %function ENTRY(stext)
-->读processor ID
调用汇编函数 __lookup_processor_type 检查内核是否支持该处理器
-->调用汇编函数 __lookup_machine_type 检查内核是否支持该目标板
-->调用汇编函数 __create_page_tables 创建页表
h 接下来调用arch/arm/mm/proc-arm920.S中的汇编函数 __arm920_setup
i 接下来返回调用arch/arm/kernel/head.s中的__enable_mmu
-->__turn_mmu_on
-->mov pc, r13即 ./arch/arm/kernel/head-common.S中的标号: __switch_data
-->再调用 __mmap_switched 完成数据段的重定位、BSS段清0、设置SP、保存全局变量
J 接下来就是b start_kernel 该函数位于init/main.c中
此次分析完毕,接开始部分。。
在分析start_kernel启动过程时,
-->setup_arch
这个函数中搜索支持的单板,然后对这三个全局变量进行赋值
init_arch_irq = mdesc->init_irq;
system_timer = mdesc->timer;
init_machine = mdesc->init_machine;
来完成对目标板的指针接口。
以后再来分析其他的驱动启动过程。。。。。。。
参考:
http://blog.csdn.net/lanmanck/article/details/4288048
http://blog.163.com/fj_ltls/blog/static/1380271112011610101118227/
http://blog.chinaunix.net/uid-20672257-id-2891129.html
Linux内核启动代码分析二之开发板相关驱动程序加载分析的更多相关文章
- JVM学习二:JVM之类加载器之加载分析
前面一遍,我们对类的加载有了一个整体的认识,而这一节我们细节分析一下类加载器的第一步,即:加载. 一.概念 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区 ...
- Linux内核启动流程分析(一)【转】
转自:http://blog.chinaunix.net/uid-25909619-id-3380535.html 很久以前分析的,一直在电脑的一个角落,今天发现贴出来和大家分享下.由于是word直接 ...
- 通过从代码层面分析Linux内核启动来探知操作系统的启动过程
通过从代码层面分析Linux内核启动来探知操作系统的启动过程 前言说明 本篇为网易云课堂Linux内核分析课程的第三周作业,我将围绕Linux 3.18的内核中的start_kernel到init进程 ...
- Linux内核启动分析
张超<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 我的代码可见https://www.shiyanlo ...
- 【内核】linux内核启动流程详细分析
Linux内核启动流程 arch/arm/kernel/head-armv.S 该文件是内核最先执行的一个文件,包括内核入口ENTRY(stext)到start_kernel间的初始化代码, 主要作用 ...
- 【内核】linux内核启动流程详细分析【转】
转自:http://www.cnblogs.com/lcw/p/3337937.html Linux内核启动流程 arch/arm/kernel/head-armv.S 该文件是内核最先执行的一个文件 ...
- Linux内核启动分析笔记
一.驱动加载 1.驱动加载调用关系 start_kernel //init/main.c rest_init //最后执行它 kernel_init //使用kernel_thread创建一个进程执行 ...
- Linux内核启动过程概述
版权声明:本文原创,转载需声明作者ID和原文链接地址. Hi!大家好,我是CrazyCatJack.今天给大家带来的是Linux内核启动过程概述.希望能够帮助大家更好的理解Linux内核的启动,并且创 ...
- linux内核启动以及文件系统的加载过程
Linux 内核启动及文件系统加载过程 当u-boot 开始执行 bootcmd 命令,就进入 Linux 内核启动阶段.普通 Linux 内核的启动过程也可以分为两个阶段.本文以项目中使用的 lin ...
随机推荐
- BZOJ 1801: [Ahoi2009]chess 中国象棋( dp )
dp(i, j, k)表示考虑了前i行, 放了0个炮的有j列, 放了1个炮的有k列. 时间复杂度O(NM^2) -------------------------------------------- ...
- [C#参考]细说进程、应用程序域与上下文之间的关系
原文转载链接:http://www.cnblogs.com/leslies2/archive/2012/03/06/2379235.html Written by:风尘浪子 引言 本文主要是介绍进程( ...
- Nginx +iis反向代理
一:简介 Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,并在一个BSD-like 协议下发行.由俄罗斯的程序设计师Igor Sysoev所 ...
- Gulp 从0开始
http://www.w3ctech.com/topic/134 (该文章有很多错误) http://markpop.github.io/2014/09/17/Gulp%E5%85%A5%E9%97 ...
- 解决android TextView多行文本(超过3行)使用ellipsize属性无效问题
布局文件中的TextView属性 <TextView android:id="@+id/businesscardsingle_content_abstract" androi ...
- Delphi 重启应用程序(创建Bat文件的Process)
Delphi 重启应用程序在工程主文件中加入Delay(500); //启动程序时请延时一段时间,否则只能重启一次 procedure RestartApp; var BatchFile: TextF ...
- CSS文字样式
font-family:通常文章的正文使用的是易读性较强的serif字体,用户长时间阅读下不easy疲劳.而标题和表格则採用较醒目的sans-serif字体.Web设计及浏览器设置中也推荐遵循此原则. ...
- HDU2159:FATE(二维完全背包)
Problem Description 最近xhd正在玩一款叫做FATE的游戏,为了得到极品装备,xhd在不停的杀怪做任务.久而久之xhd开始对杀怪产生的厌恶感,但又不得不通过杀怪来升完这最后一级.现 ...
- sql server中关于批处理与脚本的简单介绍
1.批处理 批处理指的是包含一条或多条T-SQL语句的语句组,这组语句从应用程序一次性地发送到SQL Server服务器执行.SQL Server服务器将批处理语句编译成一个可执行单元(即执行计划), ...
- HTML标记语言和CSS样式的简单运用(Nineteenth Day)
曾经励志下去要坚持把每天所学的知识记录下来,可是坚持了几天后,就觉得自己坚持不下去了....这几天自己好好的想了想,觉得不能坚持也得要坚持,因为要对自己负责,所以得学会逼着自己去做,只有这样才能把一件 ...