一、实验内容

为了熟悉使用qemu和gdb进行的调试工作,我们进行如下的小练习:

(一)从CPU加电后执行的第一条指令开始,单步跟踪BIOS的执行。

(二)在初始化位置0x7c00设置实地址断点,测试断点正常。

(三)从0x7c00开始跟踪代码运行,将单步跟踪反汇编得到的代码与bootasm.S和 bootblock.asm进行比较。

(四)自己找一个bootloader或内核中的代码位置,设置断点并进行测试。

二、实验步骤

补充材料:

我们主要通过硬件模拟器qemu来进行各种实验。在实验的过程中我们可能会遇上各种各样的问题,调试是必要的。qemu支持使用gdb进行的强大而方便的调试。所以用好qemu和gdb是完成各种实验的基本要素

默认的gdb需要进行一些额外的配置才进行qemu的调试任务。qemu和gdb之间使用网络端口1234进行通讯。在打开qemu进行模拟之后,执行gdb并输

target remote :1234

即可连接qemu,此时qemu会进入停止状态,听从gdb的命令

另外,我们可能需要qemu在一开始便进入等待模式,则我们不再使用make qemu开始系统的运行,而使用make debug来完成这项工作。这样qemu便不会在gdb尚未连接的时候擅自运行了。

gdb的地址断点

在gdb命令行中,使用b *[地址]便可以在指定内存地址设置断点,当qemu中的cpu执行到指定地址时,便会将控制权交给gdb。

(一)从CPU加电后执行的第一条指令开始,单步跟踪BIOS的执行

1.修改gdbinit文件

首先,在 /moocos/ucore_lab/labcodes_answer/lab1_result/tools  目录下,修改gdbinit文件

进入目录:

cd ./moocos/ucore_lab/labcodes_answer/lab1_result/tools

修改方法为:

输入vim gdbinit

用D删除gdbinit中原有的内容(D为删除整行,x或X为删除单个字符)

将以下内容粘贴入gdbinit中

set architecture i8086
target remote :1234 

2.make debug

输入cd ..,退回到./moocos/ucore_lab/labcodes_answer/lab1_result

输入make debug

随后执行make debug,将弹出gdb窗口,如图所示:

在gdb窗口中使用si命令即可单步追踪

(注意:你不必每次输入si,输入一次si后,只要按回车即可执行上次的指令)

在gdb界面下,可通过如下命令来看BIOS的代码

x /2i $pc(显示当前eip处的汇编指令)

 (二)在初始化位置0x7c00设置实地址断点,测试断点正常。

1.修改gdbinit文件

进入目录:

cd ./moocos/ucore_lab/labcodes_answer/lab1_result/tools

修改方法与(一)相同,

修改的内容如下:

target remote :1234     //连接qemu,此时qemu会进入停止状态,听从gdb的命令
set architecture i8086 //设置当前调试的CPU是8086
b *0x7c00 //在0x7c00处设置断点。此地址是bootloader入口点地址,可看boot/bootasm.S的start地址处
c //continue简称,表示继续执行
x/10i $pc //显示当前eip处的汇编指令

2.make debug 

输入cd ..,退回到./moocos/ucore_lab/labcodes_answer/lab1_result

输入make debug

(三)从0x7c00开始跟踪代码运行,将单步跟踪反汇编得到的代码与bootasm.S和 bootblock.asm进行比较

bootasm.S的完整代码为:

 #include <asm.h>

 # Start the CPU: switch to -bit protected mode, jump into C.
# The BIOS loads this code from the first sector of the hard disk into
# memory at physical address 0x7c00 and starts executing in real mode
# with %cs= %ip=7c00. .set PROT_MODE_CSEG, 0x8 # kernel code segment selector
.set PROT_MODE_DSEG, 0x10 # kernel data segment selector
.set CR0_PE_ON, 0x1 # protected mode enable flag # start address should be :7c00, in real mode, the beginning address of the running bootloader
.globl start
start:
.code16 # Assemble for -bit mode
cli # Disable interrupts
cld # String operations increment # Set up the important data segment registers (DS, ES, SS).
xorw %ax, %ax # Segment number zero
movw %ax, %ds # -> Data Segment
movw %ax, %es # -> Extra Segment
movw %ax, %ss # -> Stack Segment # Enable A20:
# For backwards compatibility with the earliest PCs, physical
# address line is tied low, so that addresses higher than
# 1MB wrap around to zero by default. This code undoes this.
seta20.:
inb $0x64, %al # Wait for not busy( input buffer empty).
testb $0x2, %al
jnz seta20. movb $0xd1, %al # 0xd1 -> port 0x64
outb %al, $0x64 # 0xd1 means: write data to 's P2 port seta20.:
inb $0x64, %al # Wait for not busy( input buffer empty).
testb $0x2, %al
jnz seta20. movb $0xdf, %al # 0xdf -> port 0x60
outb %al, $0x60 # 0xdf = , means set P2's A20 bit(the 1 bit) to 1 # Switch from real to protected mode, using a bootstrap GDT
# and segment translation that makes virtual addresses
# identical to physical addresses, so that the
# effective memory map does not change during the switch.
lgdt gdtdesc
movl %cr0, %eax
orl $CR0_PE_ON, %eax
movl %eax, %cr0 # Jump to next instruction, but in -bit code segment.
# Switches processor into -bit mode.
ljmp $PROT_MODE_CSEG, $protcseg .code32 # Assemble for -bit mode
protcseg:
# Set up the protected-mode data segment registers
movw $PROT_MODE_DSEG, %ax # Our data segment selector
movw %ax, %ds # -> DS: Data Segment
movw %ax, %es # -> ES: Extra Segment
movw %ax, %fs # -> FS
movw %ax, %gs # -> GS
movw %ax, %ss # -> SS: Stack Segment # Set up the stack pointer and call into C. The stack region is from --start(0x7c00)
movl $0x0, %ebp
movl $start, %esp
call bootmain # If bootmain returns (it shouldn't), loop.
spin:
jmp spin # Bootstrap GDT
.p2align # force byte alignment
gdt:
SEG_NULLASM # null seg
SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff) # code seg for bootloader and kernel
SEG_ASM(STA_W, 0x0, 0xffffffff) # data seg for bootloader and kernel gdtdesc:
.word 0x17 # sizeof(gdt) -
.long gdt # address gdt

bootasm.S

bootblock.asm的完整代码为:

 obj/bootblock.o:     file format elf32-i386

 Disassembly of section .text:

 00007c00 <start>:

 # start address should be :7c00, in real mode, the beginning address of the running bootloader
.globl start
start:
.code16 # Assemble for -bit mode
cli # Disable interrupts
7c00: fa cli
cld # String operations increment
7c01: fc cld # Set up the important data segment registers (DS, ES, SS).
xorw %ax, %ax # Segment number zero
7c02: c0 xor %eax,%eax
movw %ax, %ds # -> Data Segment
7c04: 8e d8 mov %eax,%ds
movw %ax, %es # -> Extra Segment
7c06: 8e c0 mov %eax,%es
movw %ax, %ss # -> Stack Segment
7c08: 8e d0 mov %eax,%ss 00007c0a <seta20.>:
# Enable A20:
# For backwards compatibility with the earliest PCs, physical
# address line is tied low, so that addresses higher than
# 1MB wrap around to zero by default. This code undoes this.
seta20.:
inb $0x64, %al # Wait for not busy( input buffer empty).
7c0a: e4 in $0x64,%al
testb $0x2, %al
7c0c: a8 test $0x2,%al
jnz seta20.
7c0e: fa jne 7c0a <seta20.> movb $0xd1, %al # 0xd1 -> port 0x64
7c10: b0 d1 mov $0xd1,%al
outb %al, $0x64 # 0xd1 means: write data to 's P2 port
7c12: e6 out %al,$0x64 00007c14 <seta20.>: seta20.:
inb $0x64, %al # Wait for not busy( input buffer empty).
7c14: e4 in $0x64,%al
testb $0x2, %al
7c16: a8 test $0x2,%al
jnz seta20.
7c18: fa jne 7c14 <seta20.> movb $0xdf, %al # 0xdf -> port 0x60
7c1a: b0 df mov $0xdf,%al
outb %al, $0x60 # 0xdf = , means set P2's A20 bit(the 1 bit) to 1
7c1c: e6 out %al,$0x60 # Switch from real to protected mode, using a bootstrap GDT
# and segment translation that makes virtual addresses
# identical to physical addresses, so that the
# effective memory map does not change during the switch.
lgdt gdtdesc
7c1e: 0f lgdtl (%esi)
7c21: 6c insb (%dx),%es:(%edi)
7c22: 7c 0f jl 7c33 <protcseg+0x1>
movl %cr0, %eax
7c24: c0 and %al,%al
orl $CR0_PE_ON, %eax
7c26: c8 or $0x1,%ax
movl %eax, %cr0
7c2a: 0f c0 mov %eax,%cr0 # Jump to next instruction, but in -bit code segment.
# Switches processor into -bit mode.
ljmp $PROT_MODE_CSEG, $protcseg
7c2d: ea 7c b8 ljmp $0xb866,$0x87c32 00007c32 <protcseg>: .code32 # Assemble for -bit mode
protcseg:
# Set up the protected-mode data segment registers
movw $PROT_MODE_DSEG, %ax # Our data segment selector
7c32: b8 mov $0x10,%ax
movw %ax, %ds # -> DS: Data Segment
7c36: 8e d8 mov %eax,%ds
movw %ax, %es # -> ES: Extra Segment
7c38: 8e c0 mov %eax,%es
movw %ax, %fs # -> FS
7c3a: 8e e0 mov %eax,%fs
movw %ax, %gs # -> GS
7c3c: 8e e8 mov %eax,%gs
movw %ax, %ss # -> SS: Stack Segment
7c3e: 8e d0 mov %eax,%ss # Set up the stack pointer and call into C. The stack region is from --start(0x7c00)
movl $0x0, %ebp
7c40: bd mov $0x0,%ebp
movl $start, %esp
7c45: bc 7c mov $0x7c00,%esp
call bootmain
7c4a: e8 b1 call 7d00 <bootmain> 00007c4f <spin>: # If bootmain returns (it shouldn't), loop.
spin:
jmp spin
7c4f: eb fe jmp 7c4f <spin>
7c51: 8d lea 0x0(%esi),%esi 00007c54 <gdt>:
...
7c5c: ff (bad)
7c5d: ff incl (%eax)
7c5f: add %al,(%eax)
7c61: 9a cf ff ff lcall $0x0,$0xffff00cf
7c68: cf add %dl,0x1700cf(%edx) 00007c6c <gdtdesc>:
7c6c: pop %ss
7c6d: 7c add %dl,0x0(%esp,%edi,)
... 00007c72 <readseg>:
/* *
* readseg - read @count bytes at @offset from kernel into virtual address @va,
* might copy more than asked.
* */
static void
readseg(uintptr_t va, uint32_t count, uint32_t offset) {
7c72: push %ebp
7c73: e5 mov %esp,%ebp
7c75: push %edi
7c76: push %esi
7c77: c6 mov %eax,%esi
7c79: push %ebx
uintptr_t end_va = va + count;
7c7a: 8d lea (%eax,%edx,),%eax // round down to sector boundary
va -= offset % SECTSIZE;
7c7d: d2 xor %edx,%edx
/* *
* readseg - read @count bytes at @offset from kernel into virtual address @va,
* might copy more than asked.
* */
static void
readseg(uintptr_t va, uint32_t count, uint32_t offset) {
7c7f: push %ebx
uintptr_t end_va = va + count;
7c80: f0 mov %eax,-0x10(%ebp) // round down to sector boundary
va -= offset % SECTSIZE;
7c83: c8 mov %ecx,%eax
7c85: f7 e4 7d divl 0x7de4
7c8b: d6 sub %edx,%esi // translate from bytes to sectors; kernel starts at sector 1
uint32_t secno = (offset / SECTSIZE) + ;
7c8d: 8d lea 0x1(%eax),%ebx // If this is too slow, we could read lots of sectors at a time.
// We'd write more to memory than asked, but it doesn't matter --
// we load in increasing order.
for (; va < end_va; va += SECTSIZE, secno ++) {
7c90: 3b f0 cmp -0x10(%ebp),%esi
7c93: jae 7cfa <readseg+0x88>
static inline void ltr(uint16_t sel) __attribute__((always_inline)); static inline uint8_t
inb(uint16_t port) {
uint8_t data;
asm volatile ("inb %1, %0" : "=a" (data) : "d" (port));
7c95: ba f7 mov $0x1f7,%edx
7c9a: ec in (%dx),%al
struct elfhdr * ELFHDR = ((struct elfhdr *)0x10000) ; // scratch space /* waitdisk - wait for disk ready */
static void
waitdisk(void) {
while ((inb(0x1F7) & 0xC0) != 0x40)
7c9b: e0 c0 and $0xffffffc0,%eax
7c9e: 3c cmp $0x40,%al
7ca0: f3 jne 7c95 <readseg+0x23>
: "memory", "cc");
} static inline void
outb(uint16_t port, uint8_t data) {
asm volatile ("outb %0, %1" :: "a" (data), "d" (port));
7ca2: b2 f2 mov $0xf2,%dl
7ca4: b0 mov $0x1,%al
7ca6: ee out %al,(%dx)
7ca7: 0f b6 c3 movzbl %bl,%eax
7caa: b2 f3 mov $0xf3,%dl
7cac: ee out %al,(%dx)
7cad: 0f b6 c7 movzbl %bh,%eax
7cb0: b2 f4 mov $0xf4,%dl
7cb2: ee out %al,(%dx)
waitdisk(); outb(0x1F2, ); // count = 1
outb(0x1F3, secno & 0xFF);
outb(0x1F4, (secno >> ) & 0xFF);
outb(0x1F5, (secno >> ) & 0xFF);
7cb3: d8 mov %ebx,%eax
7cb5: b2 f5 mov $0xf5,%dl
7cb7: c1 e8 shr $0x10,%eax
7cba: 0f b6 c0 movzbl %al,%eax
7cbd: ee out %al,(%dx)
outb(0x1F6, ((secno >> ) & 0xF) | 0xE0);
7cbe: d8 mov %ebx,%eax
7cc0: b2 f6 mov $0xf6,%dl
7cc2: c1 e8 shr $0x18,%eax
7cc5: e0 0f and $0xf,%eax
7cc8: c8 e0 or $0xffffffe0,%eax
7ccb: ee out %al,(%dx)
7ccc: b0 mov $0x20,%al
7cce: b2 f7 mov $0xf7,%dl
7cd0: ee out %al,(%dx)
static inline void ltr(uint16_t sel) __attribute__((always_inline)); static inline uint8_t
inb(uint16_t port) {
uint8_t data;
asm volatile ("inb %1, %0" : "=a" (data) : "d" (port));
7cd1: ba f7 mov $0x1f7,%edx
7cd6: ec in (%dx),%al
struct elfhdr * ELFHDR = ((struct elfhdr *)0x10000) ; // scratch space /* waitdisk - wait for disk ready */
static void
waitdisk(void) {
while ((inb(0x1F7) & 0xC0) != 0x40)
7cd7: e0 c0 and $0xffffffc0,%eax
7cda: 3c cmp $0x40,%al
7cdc: f3 jne 7cd1 <readseg+0x5f> // wait for disk to be ready
waitdisk(); // read a sector
insl(0x1F0, dst, SECTSIZE / );
7cde: 8b 0d e4 7d mov 0x7de4,%ecx
return data;
} static inline void
insl(uint32_t port, void *addr, int cnt) {
asm volatile (
7ce4: f7 mov %esi,%edi
7ce6: ba f0 mov $0x1f0,%edx
7ceb: c1 e9 shr $0x2,%ecx
7cee: fc cld
7cef: f2 6d repnz insl (%dx),%es:(%edi)
uint32_t secno = (offset / SECTSIZE) + ; // If this is too slow, we could read lots of sectors at a time.
// We'd write more to memory than asked, but it doesn't matter --
// we load in increasing order.
for (; va < end_va; va += SECTSIZE, secno ++) {
7cf1: e4 7d add 0x7de4,%esi
7cf7: inc %ebx
7cf8: eb jmp 7c90 <readseg+0x1e>
readsect((void *)va, secno);
}
}
7cfa: pop %eax
7cfb: 5b pop %ebx
7cfc: 5e pop %esi
7cfd: 5f pop %edi
7cfe: 5d pop %ebp
7cff: c3 ret 00007d00 <bootmain>: /* bootmain - the entry of bootloader */
void
bootmain(void) {
// read the 1st page off disk
readseg((uintptr_t)ELFHDR, SECTSIZE * , );
7d00: a1 e4 7d mov 0x7de4,%eax
7d05: c9 xor %ecx,%ecx
}
} /* bootmain - the entry of bootloader */
void
bootmain(void) {
7d07: push %ebp
7d08: e5 mov %esp,%ebp
7d0a: push %esi
// read the 1st page off disk
readseg((uintptr_t)ELFHDR, SECTSIZE * , );
7d0b: 8d c5 lea 0x0(,%eax,),%edx
7d12: a1 e0 7d mov 0x7de0,%eax
}
} /* bootmain - the entry of bootloader */
void
bootmain(void) {
7d17: push %ebx
// read the 1st page off disk
readseg((uintptr_t)ELFHDR, SECTSIZE * , );
7d18: e8 ff ff ff call 7c72 <readseg> // is this a valid ELF?
if (ELFHDR->e_magic != ELF_MAGIC) {
7d1d: a1 e0 7d mov 0x7de0,%eax
7d22: 7f 4c cmpl $0x464c457f,(%eax)
7d28: 3a jne 7d64 <bootmain+0x64>
} struct proghdr *ph, *eph; // load each program segment (ignores ph flags)
ph = (struct proghdr *)((uintptr_t)ELFHDR + ELFHDR->e_phoff);
7d2a: 8b 1c mov 0x1c(%eax),%ebx
7d2d: c3 add %eax,%ebx
eph = ph + ELFHDR->e_phnum;
7d2f: 0f b7 2c movzwl 0x2c(%eax),%eax
7d33: c1 e0 shl $0x5,%eax
7d36: 8d lea (%ebx,%eax,),%esi
for (; ph < eph; ph ++) {
7d39: f3 cmp %esi,%ebx
7d3b: jae 7d55 <bootmain+0x55>
readseg(ph->p_va & 0xFFFFFF, ph->p_memsz, ph->p_offset);
7d3d: 8b mov 0x8(%ebx),%eax
struct proghdr *ph, *eph; // load each program segment (ignores ph flags)
ph = (struct proghdr *)((uintptr_t)ELFHDR + ELFHDR->e_phoff);
eph = ph + ELFHDR->e_phnum;
for (; ph < eph; ph ++) {
7d40: c3 add $0x20,%ebx
readseg(ph->p_va & 0xFFFFFF, ph->p_memsz, ph->p_offset);
7d43: 8b 4b e4 mov -0x1c(%ebx),%ecx
7d46: 8b f4 mov -0xc(%ebx),%edx
7d49: ff ff ff and $0xffffff,%eax
7d4e: e8 1f ff ff ff call 7c72 <readseg>
7d53: eb e4 jmp 7d39 <bootmain+0x39>
} // call the entry point from the ELF header
// note: does not return
((void (*)(void))(ELFHDR->e_entry & 0xFFFFFF))();
7d55: a1 e0 7d mov 0x7de0,%eax
7d5a: 8b mov 0x18(%eax),%eax
7d5d: ff ff ff and $0xffffff,%eax
7d62: ff d0 call *%eax
asm volatile ("outb %0, %1" :: "a" (data), "d" (port));
} static inline void
outw(uint16_t port, uint16_t data) {
asm volatile ("outw %0, %1" :: "a" (data), "d" (port));
7d64: b8 8a ff ff mov $0xffff8a00,%eax
7d69: c2 mov %eax,%edx
7d6b: ef out %ax,(%dx)
7d6d: b8 8e ff ff mov $0xffff8e00,%eax
7d72: ef out %ax,(%dx)
7d74: eb fe jmp 7d74 <bootmain+0x74>

bootblock.asm

反汇编得到的代码是:

下图是bootasm.S中14到28行的代码:

下面是bootblock.asm中10到25行的代码

比较可知,三者基本一致。

(四)自己找一个bootloader或内核中的代码位置,设置断点并进行测试

  

Lab_1:练习2——使用qemu执行并调试lab1中的软件的更多相关文章

  1. 使用 visualstudio code 编辑器调试执行在 homestead 环境中的 laravel 程序

    由于之前做 .net 开发比较熟悉 visualstudio,所以自 visualstudio code 发布后就一直在不同场合使用 vscode ,比如前端.node等等.最近在做 laravel ...

  2. 关于真机调试DDMS中的data文件夹打不开的解决方法

    关于真机调试DDMS中的data文件夹打不开的解决方法 今天在开发的时候需要导出程序中的数据库文件查看数据,数据库文件默认就在/data/data/应用包名/databases/数据库名 这个路径下, ...

  3. 在 Visual Studio 调试器中指定符号 (.pdb) 和源文件

    查找并指定符号文件和源文件:指定符号加载行为.使用符号和源服务器上:加载符号自动或在要求.   内容 查找符号 (.pdb) 文件 查找源文件   查找符号 (.pdb) 文件 说明 在之前的 Vis ...

  4. 转:使用IDA动态调试WanaCrypt0r中的tasksche.exe

    逆向分析——使用IDA动态调试WanaCrypt0r中的tasksche.exe 转:http://www.4hou.com/technology/4832.html 2017年5月19日发布 导语: ...

  5. @清晰掉 GDB调试器中的战斗机

    GDB 的命令很多,本文不会全部介绍,仅会介绍一些最常用的.在介绍之前,先介绍GDB中的一个非常有用的功能:补齐功能.它就如同Linux下SHELL中的命令补齐一样.当你输入一个命令的前几个字符,然后 ...

  6. 在VisualStudio调试器中使用内存窗口和查看内存分布

    调试模式下内存窗口的使用 在调试期间,"内存"窗口显示应用使用的内存空间.调试器窗口(如"监视"."自动"."局部变量" ...

  7. 异步控制---实现函数asyncAll,在执行完传入数组中func1,func2,func3异步函数后,输出“end”

    实现函数asyncAll,在执行完传入数组中func1,func2,func3异步函数后,输出"end" function func1(callback) { setTimeout ...

  8. 在执行xp_cmdshell的过程中出错,调用'LogonUserW'失败,错误代码:'1909'

    在上篇文章Could not obtain information about Windows NT group/user 'xxxx\xxxx', error code 0x5里面,我介绍了SQL ...

  9. 重新想象 Windows 8 Store Apps (42) - 多线程之线程池: 延迟执行, 周期执行, 在线程池中找一个线程去执行指定的方法

    [源码下载] 重新想象 Windows 8 Store Apps (42) - 多线程之线程池: 延迟执行, 周期执行, 在线程池中找一个线程去执行指定的方法 作者:webabcd 介绍重新想象 Wi ...

随机推荐

  1. javascript(六)运算符

    运算符概述 JavaScript中的运算符用于算术表达式. 比较表达式. 逻辑表达式. 赋值表达式等.需要注意的是, 大多数运算符都是由标点符号表示的, 比如 "+" 和" ...

  2. vi/vim的快捷操作(2)

    1.拷贝当前行[yy],拷贝当前行向下的5行[5yy],并粘贴[p] 2.删除当前行[dd],删除当前行向下的5行[5dd] 3.在文件中查找某个单词,命令行模式下输入[/关键字],回车查找,输入[n ...

  3. 记录下vue keep-alive IOS下无法保存滚动scroll位置的问题

    最近 做的项目,遇到了一点小麻烦,就是我一个页面A页面是加载 列表数据 ,B页面是展示详细信息的.A进去B时,缓存A页面. 效果 做出来 后,缓存是缓存数据 了,但是当我A页面的列表数据 好多,要滚动 ...

  4. CTF-代码审计(3)..实验吧——你真的会PHP吗

    连接:http://ctf5.shiyanbar.com/web/PHP/index.php 根据题目应该就是代码审计得题,进去就是 日常工具扫一下,御剑和dirsearch.py 无果 抓包,发现返 ...

  5. bugku秋名山老司机+写博客的第一天

    bugku之秋名山老司机 题目连接:http://123.206.87.240:8002/qiumingshan/ 一点进去是这样的 请在两秒内计算这个式子...怎么可能算的出来 查看源码,无果.. ...

  6. PostgreSQL SQL HINT的使用说明

    本文来自:http://www.023dns.com/Database_mssql/5974.html PostgreSQL优化器是基于成本的 (CBO) , (当然, 如果开启了GEQO的话, 在关 ...

  7. MySQL数据物理备份之lvm快照

    使用lvm快照实现物理备份 优点: 几乎是热备(创建快照前把表上锁,创建完后立即释放) 支持所有存储引擎 备份速度快 无需使用昂贵的商业软件(它是操作系统级别的) 缺点: 可能需要跨部门协调(使用操作 ...

  8. Linux服务管理之ntp

    NTP是网络时间协议(Network Time Protocol),它是用来同步网络中各个计算机的时间的协议. 在计算机的世界里,时间非常地重要,例如对于火箭发射这种科研活动,对时间的统一性和准确性要 ...

  9. 解决Mac下SourceTree每次都让输入密码的问题

    在Mac上操作sourcetree当pull和push时每次都是让输入密码,非常烦人,虽然大概知道是因为SSH什么的问题,但搜索百度也没发现解决办法. 于是乎搜索谷歌,发现如下解决办法. Source ...

  10. Python并发编程内容回顾

    Python并发编程内容回顾 并发编程小结 目录 • 一.到底什么是线程?什么是进程? • 二.Python多线程情况下: • 三.Python多进程的情况下: • 四.为什么有这把GIL锁? • 五 ...