开发者社区> sky-heaven> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

一个简单多任务内核实例的分析【转】

简介: 转自:http://blog.csdn.net/rosetta/article/details/8933228 一个简单多任务内核实例的分析 转载请注明出处:http://blog.csdn.net/rosetta   简介 Linux-0.00是由Linus Torvalds写的Linux最初版本(未发布),只是打印AAA和BBB而没有更多的功能,比如内存管理、文件系统、字符设备驱动程序等,而Linux-0.11是一个比较完整的内核,也包含上述内容。
+关注继续查看

转自:http://blog.csdn.net/rosetta/article/details/8933228

一个简单多任务内核实例的分析

转载请注明出处:http://blog.csdn.net/rosetta

 

简介

Linux-0.00是由Linus Torvalds写的Linux最初版本(未发布),只是打印AAA和BBB而没有更多的功能,比如内存管理、文件系统、字符设备驱动程序等,而Linux-0.11是一个比较完整的内核,也包含上述内容。

先分析Linux-0.00而不是Linux-0.11是因为前者是后者的基础,它非常精简但又能涵括几乎所有操作系统的基础知识,在真实完全明白Linux-0.00后再分析Linux-0.11就相对容易很多。

Linux-0.00的原始源码在redhat9.0中无法编译,但在《Linux内核完全剖析》一书中其作者给出了可以在redhat9.0中编译通过的Linux-0.00版本。

在分析任何代码之前如果可以我一般会先搭建环境,搭建环境的过程就会对整个操作过程有大概映像,如何搭建环境文档如下。http://blog.csdn.net/rosetta/article/details/8933240

 

Linux-0.00包含两个特权级3的用户任务和一个系统调用中断过程。其由两个文件组成:as86汇编语言写的boot.s(引导启动程序)和GNU as汇编写的head.s(多任务内核程序)。前者只是引导程序,把head.s代码加载进内存并把控制权转移到head.s中执行;后者实现两个特权级3上的任务在时钟中断控制下相互切换运行,并实现显示字符的系统调用。

任务A(0)不停的打印“AAA……”,当遇到时钟中断后切换到任务B(1)中运行打印“BBB……”,再遇时钟中断再打印“AAA……”,如此循环。

boot.s编译出的代码共544Bytes,由Makefile处理后剩余512B存放在软盘映像文件的第一扇区中(就是《Linux-0.00运行环境搭建》中的Image,这里使用的环境能用不用实际的软盘就不用,因为现在的计算机都没软区,以后考虑使用USB启动是挺不错的)。Image是由Makefile通过dd等命令把boot.s和head.s编译出来的二进制文件合起来的,在Linux-0.11中是由build.c完成合并操作的。

PC加电启动时,ROM BIOS中的程序会把启动盘(Image可被bochs直接启动,相当于启动盘)上第一个扇区加载到物理内存0x7c00处,并把程序执行权移到0x7c00处开始运行boot代码。boot的主要功能是把映像文件中的head内核代码先加载到内存的0x10000处,再把head搬到0x0处,并设置好临时GDT等信息后,把处理器设置成运行在保护模式下,再跳转到head中执行。这里有一个问题是boot为什么要把head先加载到0x10000再移到0x0处,而不直接加载到0x0处,这是因为把映像文件加载到内存中时需要使用BIOS INT13中断,而BIOS在初始化时会把中断向量表放在内存0x0处。

BOOT.S源码分析

完整的boot.s共66行,包括注释和空行,带行号。所有新增的分析都写在代码旁边,不带行号。

  1 !   boot.s

  2 

  3 BOOTSEG = 0x07c0

  4 SYSSEG  = 0x1000            ! system loaded at 0x10000 (65536).

  5 SYSLEN  = 17                ! sectors occupied.

  6 

  7 entry start

  8 start:

  9     jmpi    go,#BOOTSEG  !跳转到段BOOTSEG,段内偏移为go的地方执行代码,此时为实模式,所以BOOTSEG为段地址,后面在保护模式下会讲到,jmpi后面跟的是偏移和段选择符。

 10 go: mov ax,cs !跳转到这里后,cs的值就为0x07c0(注意,段地址在实际转换过程是需要左移4位,即多一个零为0x07c00,以下出些段的地方类似)

 11     mov ds,ax

 12     mov ss,ax

 13     mov sp,#0x400       ! arbitrary value >>512

 14  !设置临时栈指针。在这个程序中好像没什么用。

!开始加载内核模块head到段0x1000处。这里主要利用BIOS 的int 13中断从第一扇区读取代码到内存。

使用BIOS int 13 02H功能时各寄存器含义,可查询《x86中断手册》。

功能描述:读扇区

入口参数:AH=02H

AL=扇区数

CH=柱面

CL=扇区

DH=磁头

DL=驱动器,00H~7FH:软盘;80H~0FFH:硬盘

ES:BX=缓冲区的地址

出口参数:CF=0——操作成功,AH=00H,AL=传输的扇区数,否则,AH=状态代码,参见功能号01H中的说明

 

 15 ! ok, we've written the message, now

 16 load_system: 

 17     mov dx,#0x0000 !DH=00H,驱动器号;DL=00H,磁头号。

 18     mov cx,#0x0002  !CH=00H,0柱面;CL=02H,从第2扇区开始读。

 19     mov ax,#SYSSEG  !ES:BX 读入到此缓冲区地址处(0x1000:0x0000)。

 20     mov es,ax

 21     xor bx,bx

 22     mov ax,#0x200+SYSLEN !AH=02H,读扇区;AL=17,需要读到第17个扇区。

 23     int     0x13

 24     jnc ok_load  !CF=0时跳转。CF=0表明读扇区操作成功。

 25 die:    jmp die !如果读取失败进入死循环,此时代码无法解除错误,只能让代码重新运行。

 26 

 27 ! now we want to move to protected mode ...

 28 ok_load:

 29     cli         ! no interrupts allowed !关中断,为什么要关?怎么开?sti开中断

 30     mov ax, #SYSSEG

 31     mov ds, ax

 32     xor ax, ax

 33     mov es, ax

 34     mov cx, #0x1000

 35     sub si,si

 36     sub di,di

 37     rep

 38     movw !ES:DI<-DS:SI(从DS段的SI偏移处移动CX个字到ES段的DI处)

!这里使用的是movw,每次移动的是双字节(一个字),一共移动cx=0x1000次=4K次,

!总共4K*2B=8192B=8KB。

!一个扇区为512B,8KB=16个扇区,所以这里一共从0x1000:0000开始处移动16个扇区大

!小到0x0000:0000处。

 39     mov ax, #BOOTSEG 

 40     mov ds, ax!因为前面修改了数据段寄存器ds的值,所以需要设置回来为0x07c0。因为lidt和lgdt是从数据段寄存指向的段中取内存中的值,如果不设置,那么此时ds为0x1000,而lidt会读0x1000:0x69处的值到idtr寄存器中,那么读取的idt肯定是错的。

正常情况是读0x07c0:0x69处的值到idtr寄存器。

 

 41     lidt    idt_48      ! load idt with 0,0 

!加载中断向量表idt的基地址和长度加载到IDTR寄存器中。

 42     lgdt    gdt_48      ! load gdt with whatever appropriate

!加载全局描述符表gdt的基地址和长度加载到IDTR寄存器中。

!基地址:0x7c00+gdt,长度:0x7ff(2047+1=2048字节,gdt中的每一项为段描述符,为8字节,所以一共有2048/8项=256项)。

 43 

 44 ! absolute address 0x00000, in 32-bit protected mode.

 45     mov ax,#0x0001  ! protected mode (PE) bit

 46     lmsw    ax      ! This is it! 设置CR0保护模式标志PE(位0).

 47     jmpi    0,8     ! jmp offset 0 of segment 8 (cs) 

!因为此时已经进入到保护模式,所以这里的8是段选择符,8=1000b,TI为0表示GDT表,索引为1,表示GDT表中的第2项(索引为0表示第1项),即下面GDT表中的第二项——代码段。

 48 

 49 gdt:    .word   0,0,0,0     ! dummy 第一项为0,不用。

 50 

 51     .word   0x07FF      ! 8Mb - limit=2047 (2048*4096=8Mb) !第1,2字节

 52     .word   0x0000      ! base address=0x00000 !第3,4字节 

 53     .word   0x9A00      ! code read/exec !第5,6字节

 54     .word   0x00C0      ! granularity=4096, 386 !第7,8字节

!段描述符结构如图所示。

!gdt中的第二项以二进制表示,高双字:0000000011000000 1001101000000000 (4 2

!          低双字:0000000000000000 0000011111111111 (1 0

!所以高双字的位23为颗粒度,置位表示单位是4KB,低双字中的位0~15为段限长=0x07FF+1

!=2KB,所以段限长最终为2KB*4KB=8MB。

!类型:1010,查P93表4-3知,为代码段,可执行/可读

! 高双字中的位12 S=1表示非系统段描述符(代码或数据段描述符)。

 

 55 

 56     .word   0x07FF      ! 8Mb - limit=2047 (2048*4096=8Mb)!第三项为数据段

 57     .word   0x0000      ! base address=0x00000

 58     .word   0x9200      ! data read/write

 59     .word   0x00C0      ! granularity=4096, 386

 60 

 61 idt_48: .word   0       ! idt limit=0

 62     .word   0,0     ! idt base=0L

 63 gdt_48: .word   0x7ff       ! gdt limit=2048, 256 GDT entries

 64     .word   0x7c00+gdt,0    ! gdt base = 07xxx

 65 .org 510

 66     .word   0xAA55       

到此boot.s已跳转到head.s的代码中执行,控制权交给head.s处理,下面看head.s源码。

head.s源码分析

head.s文件一共256行,GNU汇编,此代码已经运行在保护模式下,代码分析总结写在此代码最后。

  1 #  head.s contains the 32-bit startup code.

  2 #  Two L3 task multitasking. The code of tasks are in kernel area,

  3 #  just like the Linux. The kernel code is located at 0x10000.

 

 

  4 SCRN_SEL    = 0x18!这些都为段选择符,如图所示。

!0x18=00011000b,表示GDT表中的第4条(因为第一条从0开始计数)

  5 TSS0_SEL    = 0x20

  6 LDT0_SEL    = 0x28

  7 TSS1_SEL    = 0X30

  8 LDT1_SEL    = 0x38

  9 .global startup_32

 10 .text

 11 startup_32:

 12     movl $0x10,%eax #因为进入保护模式,0x10为段选择符,GDT中的第二项,0x10指之前boot中设置的GDT第三项数据段。

 13     mov %ax,%ds #数据段寄存器ds中被赋予了0x10段选择符。

 14     lss init_stack,%esp #设置系统堆栈段,ss:esp<-init_stack

 15

 16 

 17 # setup base fields of descriptors.

 18     call setup_idt  !重新设置IDT表

 19     call setup_gdt !重新设置GDT表

 20     movl $0x10,%eax     # reload all the segment registers#重新加载所有段寄存器。

 21     mov %ax,%ds     # after changing gdt.

 22     mov %ax,%es

 23     mov %ax,%fs

 24     mov %ax,%gs

 25     lss init_stack,%esp #重新设置系统堆栈段,ss:esp<-init_stack

 26 

 27 # setup up timer 8253 chip.#设置8253定时芯片,这部分先不管,其作用是每隔10秒向中断控制器发送一个中断请求。

 28     movb $0x36, %al

 29     movl $0x43, %edx

 30     outb %al, %dx #向8253芯片控制字寄存器写端口。

 31     movl $11930, %eax        # timer frequency 100 HZ #??

 32     movl $0x40, %edx

 33     outb %al, %dx

 34     movb %ah, %al

 35     outb %al, %dx

 36 

 37 # setup timer & system call interrupt descriptors.

 38     movl $0x00080000, %eax #看42、43行,这是中断门描述符,P95页。8表示段选择(1000,即之前设置好的GDT中的第2项,代码段),P104页,调用门描述符格式。

 39     movw $timer_interrupt, %ax  #

 40     movw $0x8E00, %dx # 类型为E=1110,即中断门

 41     movl $0x08, %ecx              # The PC default timer int. 

 42     lea idt(,%ecx,8), %esi #加载idt的基地址到esi中。#用ecx的值批明IDT表中的第几项?后面的8代码是每一项八字节?

 43     movl %eax,(%esi) #给IDT表中的第8项的低四节地址赋值。

 44     movl %edx,4(%esi) #给IDT表中的第8项的高四节地址赋值。

 45     movw $system_interrupt, %ax

 46     movw $0xef00, %dx

 47     movl $0x80, %ecx

 48     lea idt(,%ecx,8), %esi

 49     movl %eax,(%esi) #这里的eax高16位没有赋值啊?还是00008。

 50     movl %edx,4(%esi)

 51 

 52 # unmask the timer interrupt.

 53 #   movl $0x21, %edx

 54 #   inb %dx, %al

55 #   andb $0xfe, %al

 56 #   outb %al, %dx

 57 

 58 # Move to user mode (task 0)

 59     pushfl

 60     andl $0xffffbfff, (%esp) #?

 61     popfl

 62     movl $TSS0_SEL, %eax #把任务0的段选择符加载到任务寄存器TR中。

 63     ltr %ax

 64     movl $LDT0_SEL, %eax #把任务0的LDT段选择符加载到局部描述符表寄存器LDTR中。

 65     lldt %ax

 66     movl $0, current #当前任务号0保存在current 中。

 67     sti #开中断

 68     pushl $0x17

 69     pushl $init_stack

 70     pushfl

 71     pushl $0x0f #1111

 72     pushl $task0

 73     iret #IP<-SS:[SP],SP<-SP+2;所以就开始执行task0。

 74 

 75 /****************************************/

 76 setup_gdt:

 77     lgdt lgdt_opcode!加载gdt表的基地址和表限长到GDTR寄存中。

 78     ret

 79 

 80 setup_idt:

 81     lea ignore_int,%edx!加载IDT表的基地址和表限长到IDTR寄存中。

 82     movl $0x00080000,%eax

 83     movw %dx,%ax        /* selector = 0x0008 = cs */

 84     movw $0x8E00,%dx    /* interrupt gate - dpl=0, present */

 85     lea idt,%edi

 86     mov $256,%ecx #设置中断描述符表中256项描述符表的默认值。

 87 rp_sidt:

 88     movl %eax,(%edi)

 89     movl %edx,4(%edi)

 90     addl $8,%edi

 91     dec %ecx

 92     jne rp_sidt

 93     lidt lidt_opcode #加载内在中的IDT相关信息到IDTR寄存中。

 94     ret

 95 

 96 # -----------------------------------

 97 write_char: #打印字符调用。这段不太明白。

 98     push %gs

 99     pushl %ebx

100 #   pushl %eax

101     mov $SCRN_SEL, %ebx #内存显示段,不知道怎么用??或者哪里可以查到相关资料?

102     mov %bx, %gs #gs?

103     movl scr_loc, %ebx

104     shl $1, %ebx

105     movb %al, %gs:(%ebx)

106     shr $1, %ebx

107     incl %ebx

108     cmpl $2000, %ebx

109     jb 1f

110     movl $0, %ebx

111 1:  movl %ebx, scr_loc

112 #   popl %eax

113     popl %ebx

114     pop %gs

115     ret

116 

117 /***********************************************/

118 /* This is the default interrupt "handler" :-) */

119 .align 2

120 ignore_int: #默认中断处理程序打印“C”。

121     push %ds

122     pushl %eax

123     movl $0x10, %eax

124     mov %ax, %ds

125     movl $67, %eax            /* print 'C' */

126     call write_char

127     popl %eax

128     pop %ds

129     iret

130 

131 /* Timer interrupt handler */

132 .align 2

133 timer_interrupt:

134     push %ds

135     pushl %eax

136     movl $0x10, %eax

137     mov %ax, %ds

138     movb $0x20, %al

139     outb %al, $0x20 #?向8259A控制寄存器引脚写命令。

140     movl $1, %eax

141     cmpl %eax, current #判断当前任务号是否是任务1,如果是任务1则切换到任务0.

142     je 1f

143     movl %eax, current

144     ljmp $TSS1_SEL, $0

145     jmp 2f

146 1:  movl $0, current

147     ljmp $TSS0_SEL, $0

148 2:  popl %eax

149     pop %ds

150     iret

151 

152 /* system call handler */

153 .align 2

154 system_interrupt:

155     push %ds

156     pushl %edx

157     pushl %ecx

158     pushl %ebx

159     pushl %eax

160     movl $0x10, %edx

161     mov %dx, %ds

162     call write_char

163     popl %eax

164     popl %ebx

165     popl %ecx

166     popl %edx

167     pop %ds

168     iret

169 

170 /*********************************************/

171 current:.long 0

172 scr_loc:.long 0

173 

174 .align 2

175 lidt_opcode:

176     .word 256*8-1       # idt contains 256 entries!IDT表长度256项*8字节/项-1。

177     .long idt       # This will be rewrite by code.!IDT表的基地址。

178 lgdt_opcode:

179     .word (end_gdt-gdt)-1   # so does gdt !gdt表的表限长,2字节长。

180     .long gdt       # This will be rewrite by code. !gdt表基地址,4字节长。

181 

182     .align 8

183 idt:    .fill 256,8,0       # idt is uninitialized !默认未初始化的IDT表为256个8字节0。

184 

185 gdt:    .quad 0x0000000000000000    /* NULL descriptor */

186     .quad 0x00c09a00000007ff    /* 8Mb 0x08, base = 0x00000 */

187     .quad 0x00c09200000007ff    /* 8Mb 0x10 */

188     .quad 0x00c0920b80000002    /* screen 0x18 - for display */

189 

190     .word 0x0068, tss0, 0xe900, 0x0 # TSS0 descr 0x20

191     .word 0x0040, ldt0, 0xe200, 0x0 # LDT0 descr 0x28

192     .word 0x0068, tss1, 0xe900, 0x0 # TSS1 descr 0x30

193     .word 0x0040, ldt1, 0xe200, 0x0 # LDT1 descr 0x38

194 end_gdt:

195     .fill 128,4,0 !这是栈空间,因为栈是向下增加的,前面的代码放在低内存处,所以高内存的栈顶的位置在后面。即init_stack标号表示栈顶位置。

196 init_stack:                          # Will be used as user stack for task0.

197     .long init_stack

198     .word 0x10 !0x10为段选择符。

199 

200 /*************************************/

201 .align 8

202 ldt0:   .quad 0x0000000000000000

203     .quad 0x00c0fa00000003ff    # 0x0f, base = 0x00000

204     .quad 0x00c0f200000003ff    # 0x17

205 

206 tss0:   .long 0             /* back link */

207     .long krn_stk0, 0x10        /* esp0, ss0 */

208     .long 0, 0, 0, 0, 0     /* esp1, ss1, esp2, ss2, cr3 */

209     .long 0, 0, 0, 0, 0     /* eip, eflags, eax, ecx, edx */

210     .long 0, 0, 0, 0, 0     /* ebx esp, ebp, esi, edi */

211     .long 0, 0, 0, 0, 0, 0      /* es, cs, ss, ds, fs, gs */

212     .long LDT0_SEL, 0x8000000   /* ldt, trace bitmap */

213 

214     .fill 128,4,0

215 krn_stk0:

216 #   .long 0

203     .quad 0x00c0fa00000003ff    # 0x0f, base = 0x00000

204     .quad 0x00c0f200000003ff    # 0x17

205 

206 tss0:   .long 0             /* back link */

207     .long krn_stk0, 0x10        /* esp0, ss0 */

208     .long 0, 0, 0, 0, 0     /* esp1, ss1, esp2, ss2, cr3 */

209     .long 0, 0, 0, 0, 0     /* eip, eflags, eax, ecx, edx */

210     .long 0, 0, 0, 0, 0     /* ebx esp, ebp, esi, edi */

211     .long 0, 0, 0, 0, 0, 0      /* es, cs, ss, ds, fs, gs */

212     .long LDT0_SEL, 0x8000000   /* ldt, trace bitmap */

213 

214     .fill 128,4,0

215 krn_stk0:

216 #   .long 0

217 

218 /************************************/

219 .align 8

220 ldt1:   .quad 0x0000000000000000

221     .quad 0x00c0fa00000003ff    # 0x0f, base = 0x00000

222     .quad 0x00c0f200000003ff    # 0x17

223 

224 tss1:   .long 0             /* back link */

225     .long krn_stk1, 0x10        /* esp0, ss0 */

226     .long 0, 0, 0, 0, 0     /* esp1, ss1, esp2, ss2, cr3 */

227     .long task1, 0x200      /* eip, eflags */

228     .long 0, 0, 0, 0        /* eax, ecx, edx, ebx */

229     .long usr_stk1, 0, 0, 0     /* esp, ebp, esi, edi */

230     .long 0x17,0x0f,0x17,0x17,0x17,0x17 /* es, cs, ss, ds, fs, gs */

231     .long LDT1_SEL, 0x8000000   /* ldt, trace bitmap */

232 

233     .fill 128,4,0

234 krn_stk1:

235 

236 /************************************/

237 task0:

238     movl $0x17, %eax

239     movw %ax, %ds

240     movb $68, %al              /* print 'A' */

241     int $0x80

242     movl $0xfff, %ecx

243 1:  loop 1b

244     jmp task0

245 

246 task1:

247     movl $0x17, %eax

248     movw %ax, %ds

249     movb $69, %al              /* print 'B' */

250     int $0x80

251     movl $0xfff, %ecx

252 1:  loop 1b

253     jmp task1

254 

255     .fill 128,4,0 

256 usr_stk1:

head.s运行在32位保护模式下,主要包括初始设置的代码,时钟中断int 0x08的过程代码,系统调用中断int 0x80的过程代码以及任务A和任务B的代码和数据。初始设置主要包括:1,重新设置GDT表;2,设置系统定时器芯片;3,重新设置IDT表并设置时钟和系统调用中断门;4,移动到任务A中执行。

在虚拟地址空间中head.s程序的内核代码和任务代码分配如下图所示。在本示例中,所有代码和数据段都对应到物理内存同一区域上,即从物理内存0开始的区域。GDT中全局代码和数据段描述符的内容都设置为:基地址为0x0000;段限长为0x07ff。因为颗粒度为1,所以实际长度为8MB。而全局显示数据段被设置成:基地址0xb8000;段限长为0x0002,所以实际长度为8KB,对应到显示内存区域上。

两个任务在LDT中代码段和数据段描述符内容都设置为:基地址0x0000;段限长0x03ff,实际长度为4MB。因此在线性地址空间中这个“内核”的代码和数据段与任务代码和数据段都从线性地址0开始并且因为没有采用分页机制,所以它们都直接对应物理地址0开始处。在head程序编译出的目标文件中以及最终得到的软件映像文件Image中,代码和数据的组织形式如下图所示。

因为处理特权级0的代码不能直接把控制权转移到特权级3的代码中执行,但中断返回操作是可以的,因此当初始化GDT、IDT和定时芯片结束后,就可以利用中断中断返回指令IRET来启动运行第1个任务。具体的方法是在初始堆栈init_stack中人工设置一个返回环境。

即把任务0的TSS段选择符加载到任务寄存器LTR中、LDT段选择符加载到LDTR中后,把任务0的用户栈指针(0x17:init_stack)和代码指针(0x0f:task0)以及标志寄存器值压入栈中,然后执行中断返回指针IRET。该指令会弹出堆栈上的堆栈指针作为任务0用户栈指针,恢复假设的任务0的标志寄存器内容,并且弹出栈中代码指针放入CS:EIP寄存中,从而开始执行任务0的代码,完成了从特权级0到特权级3代码的控制转移。

为了每隔10毫秒切换运行的任务,head.s程序把定时器芯片8253的通道0设置成每经过10毫秒就向中断控制芯片8259A发送一个时钟中断请求信号。PC机的ROM 了BIOS开机时已经在8259A中把时钟中断请求信号设置成中断风和向量8,因为我们需要在中断8的处理过程中执行任务切换操作。任务切换的实现方法是查看current变量中当前运行任务号。如果current为0,就利用任务1的TSS选择符作为操作数执行远跳转指令,从而切换到任务1中执行,否则反之。

每个任务在执行时,会首先把一个字符的ASCII码放入寄存器AL中,然后调用系统中断调用int 0x80,而该系统调用处理过程会调用一个简单的字符写屏子程序,把寄存器AL中的字符显示在屏幕上,同时把字符显示的屏幕的下一个位置记录下来。在显示过一个字符后,任务代码使用循环语句延迟一段时间,然后又跳转到任务代码开始处继续循环执行,直到运行了10毫秒而发生了定时中断,从而代码会切换到另一个任务去运行。

【作者】张昺华
【新浪微博】 张昺华--sky
【twitter】 @sky2030_
【facebook】 张昺华 zhangbinghua
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
如何保证只有一个进程实例
作者:gfree.wind@gmail.com博客:blog.focus-linux.net   linuxfocus.blog.chinaunix.net  微博:weibo.com/glinuxer QQ技术群:4367710   本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。
931 0
函数指针简单的列子
关于函数指针的简单例子 函数指针简单格式如下: int (*p)(int i, char c) 首先它是一个指针,其次它是指向一个函数地址的指针,再次这个函数的返回值为int数值.
602 0
【IOS】利用ASIHTTPRequest 实现一个简单的登陆验证
【原文地址:http://blog.csdn.net/toss156/article/details/7638529】 今天给大家带来一个简单的登陆验证,用的是ASIHttpRequest 这个开源类库,使用的方法很简单,从网上下载下来以后,添加到项目中,并添加一下这些框架。
754 0
一个JSON的实例
&lt;html&gt;   &lt;head&gt;     &lt;title&gt;demo.html&lt;/title&gt;     &lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8" /&gt;     &lt;style type="text/css"&gt;     
1019 0
创建外键的一个简单例子
有两个数据库表: Product表,表中字段如下: ProductID Name Price Size ProductCategoryID   ProductCategory表,表中字段如下:...
566 0
+关注
sky-heaven
我是一个技术爱好者,喜欢分享交流技术心得
1191
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载
冬季实战营第三期:MySQL数据库进阶实战
立即下载