Skip to main content

ComputerOrgnization

Author
quanye
Systems software: OS, networking, distributed systems.
Table of Contents

Computer Organization
#

[!NOTE]

Before this post, I had already built a very simple CPU (mostly digital-logic material, though it is also part of computer organization). Course notes and materials come from Bilibili: Cs Primer.

Here I only record some theoretical points that felt worth keeping; I will keep adding to this note.

Cache (Memory Hierarchy)
#

  1. DRAM, SRAM, SSD, and HDD — their read/write behavior and storage roles, and the gap between main memory and the CPU.

  2. Program locality: programs tend to reference data items near recently referenced ones, or the same items again (e.g., iterating an array).

a. Spatial locality b. Temporal locality

Introduction:

image-20250304202823299

Cache Memory
#

1. Structure
#

image-20250306193154854

SBE is the total size.

Addressing principle: hash

image-20250306193715440

2. Direct-Mapped Cache
#

Each set contains only one line.

image-20250306194358596

Process

a. Set selection

Why use the middle bits as the set index instead of the higher bits?

If high bits were used as the index, consecutive blocks would easily map into the same cache set, which hurts spatial locality.

Middle bits tend to have more randomness.

image-20250306194739351

b. After finding the set, perform line matching

image-20250306194957319

c. Use the block offset to locate the first byte

d. On a miss, fetch the corresponding block from the next level of memory and replace

e. In practice: the cache starts empty → cold miss → load data from L2 into L1 and return; later, if the tag mismatches → conflict miss → replace.

d. A common conflict-miss pattern: thrashing — the cache repeatedly loads and evicts the same sets.

3. Set-Associative Cache
#

Each set contains more than one line.

image-20250306203058530

Match against each line in the set.

LFU and LRU policies

4. Fully Associative Cache
#

There is only one set, containing many lines.

image-20250306203614011

No set selection — only tag and block offset.

Single-Cycle vs Multi-Cycle Processors
#

A single-cycle processor completes one instruction in one clock cycle.

A multi-cycle processor spreads one instruction across multiple clock cycles.

Pipelining
#

image-20250220143616648

Five-Stage Pipeline
#

Fill each instruction into five stages to avoid conflicts.

Pipeline Hazards
#

Structural hazard: conflicts over hardware resources
#

Data hazard: conflicts from logical data dependencies
#

Control hazard: a branch to another instruction invalidates instructions already prepared in the pipeline
#

Solution: branch prediction (dynamic)


Writing and Running Programs on x86
#

Processing a C Program
#

image-20250222132826407

Preprocess → compile → assemble → link → load and execute

Suppose we have main.c and hello.c

gcc main.c hello.c	//这一条指令包含了上述的四个步骤
gcc -E hello.c -o hello.i //这表示对于文件进行预处理 -o是指定名称(擦除并且进行复制粘贴的流程)
gcc -S hello.i -o hello.s	//把预处理之后的文件处理成汇编代码
gcc -c hello.s -o hello.o	//汇编成一个二进制文件,但是不进行链接的操作

Tools: readelf (inspect section offsets), objdump (disassemble), hexdump (inspect machine code in binaries)

gcc main.o hello.o	//直接将两个文件进行链接,如下是链接的过程

image-20250222135247623

Then the load-and-execute flow:

image-20250222140136354

Common x86 Assembly Instructions
#

Refer to CSAPP for basic syntax — being able to read it is enough.

[!TIP]

Conditional jump instructions (the jmp family) can be preceded by many other instructions.

For example, after subl a,b you can also branch based on the relation between a and b.

Registers Used in 64-bit Mode
#

image-20250223163504717

Data Movement Instructions
#

move: cannot transfer directly from memory to memory
#

[!NOTE]

I have read the book several times, and the hardest part for me is still function calls and recursion. I suggest starting from push and working carefully from there.

push instruction:
#

  1. Subtract 8 from the stack pointer to get the new top-of-stack location (still empty). 2. Place the source operand at the top of the stack. (push itself is just one byte of instruction; the stack simply holds data — it is not inherently tied to registers or memory beyond that.)

pop is the reverse: 1. Read the top-of-stack value into a destination register. 2. Add 8 to the stack pointer.

Conditional Control
#

if, for, while, and similar constructs are implemented with conditional jumps.

For switch, when the case range is large it also uses conditional jumps; when the range is small (and there are many cases), it uses a jump table — a contiguous array whose domain covers all cases.

*Process (Procedures)
#

Control + data passing + memory management

Runtime Stack (preparation)
#

image-20250223172641504

When P calls Q, it first stores the return address — where P should resume when Q returns. That address is also part of P’s stack frame.

Then a stack frame is allocated for Q. Most frames have fixed size. Arguments are passed in registers; if there are more than six, P stores the extras in its own frame before calling Q.

Transferring Control (handing off execution)
#

call: set rip to the callee’s entry address, transferring control, and push the address of the next instruction after call onto the stack.

ret: pop that address and set rip to it, returning control.

Data Transfer (passing arguments to the callee)
#

With fewer than six arguments, pass them in registers and take the return value in rax.

In the Current frame diagram above, there is an Argument build area. If this function will itself call another function with more than six arguments, it prepares those extras in its own frame before call (note: the 7th argument sits at the top of the stack).

Local Storage on the Stack (how callee locals work)
#

Examples: too many locals, needing the address of a local, or locals that are arrays/structs.

Still referring to the figure above: above the argument-build area is the space obtained by decreasing the stack pointer for locals.

Example from CSAPP:

long swap_add (long *XP , long * yp)
{
long x = *xp ;
long y = * yp ;
*xp = y ;
*yp = x ;
return x + y ;
}

long caller ()
{
//要处理以下两个局部变量,我就要为他们产生地址。
long argl = 534 ;
long arg2 = 1057 ;
long sum = swap_add (&argl , &arg2) ;
long diff = argl - arg2 ;
return sum * diff;
}

Assembly:

long caller()
caller:
subq $16 , %rsp
movq $534 , (%rsp)
movq $1057 , 8(%rsp)
leaq 8(%rsp) , %rsi
movq %rsp , %rdi
call swap_add	;这里的细节:方法虽然已经返回(返回之后之前压入的返回地址就会被弹出),但是栈帧还在,所以分配的局部变量还在
movq (%rsp) , %rdx
subq 8(%rsp) ,%rdx 
imulq %rdx , %rax
addq $16 , %rsp	;此时栈帧不存在,会被后来的data覆盖掉
ret

What is the allocated stack frame used for?

See the figure below:

Allocate a frame first for this function’s locals; then push arguments beyond the sixth from right to left; then call. Do not confuse locals with passed arguments — the callee does not use locals from the caller’s frame.

image-20250223221347529

Local Storage in Registers
#

🔹 What are these registers mainly used for?
#
1. Storing local variables
#

(This is another way to store locals — e.g., a loop index.)

If a function has local variables but not enough registers, the compiler may keep some in callee-saved registers to avoid frequent stack accesses (faster than stack-resident variables).

2. Keeping long-lived variables
#

If a variable is used throughout the function’s lifetime rather than as temporary data, it may live in %rbx, %r12%r15.

3. Maintaining the frame pointer (%rbp)
#

Modern compilers may omit the frame pointer (Frame Pointer Omission, FPO), but in debug builds %rbp still holds the current function’s stack base, helping unwind the call stack.

4. Passing values across function calls
#

When a value must remain unchanged across multiple calls, it may go in a callee-saved register, for example:

  • In recursive functions, some parameters may need to survive many recursive calls.
  • In coroutines or context-switch code, some registers may hold task-specific state.

Recursion
#

At this point, recursion is straightforward: calling yourself is like calling any other procedure — each invocation has its own private stack frame.

What makes recursion hard is when the contents of each frame become complex.

Array Allocation and Access
#

  1. Pointer access: use leaq for effective addresses, mov to load values.

  2. Multidimensional arrays

  3. Fixed- and variable-length arrays, and structs

Buffer Overflow
#

C Language Basics
#

Bit-manipulation tricks

Multi-line macros use \; wrap the body in do {......} while(0) to consume the trailing ; (a subtle detail).

Inline functions: similar to macros — the call site expands the body without a jump, saving overhead (compiler-dependent).

static inline int(...){......}	//一般这样定义在头文件里使用

I will not elaborate further on C itself.

Floating-Point Numbers in Detail
#

Floating-Point Representation
#

(Floating radix point) base conversion (digital-logic material)

[!NOTE]

IEEE 754 — the classic of classics

image-20250222142456570

Here the significand (mantissa) is the binary fraction after the point:

e.g., 2.5 = 10.1b

i.e. $$ 2.5 = 1.01*2^1 $$

[!NOTE]

This is a normalized floating-point number. Normalization assumes a leading 1 (value ≥ 1 in the significand form), so we only store the fractional part in the 23-bit field. If the exponent field is 0 but the significand is nonzero, the number is denormalized; the computation rules change — the exponent becomes 1−bias — to provide a smooth transition toward zero.

Denormals and Rounding
#

image-20250222144100402

Rounding: round-to-nearest, ties to even (prefer 0 in the tie case as described in the source material)

Larger exponents mean lower density and thus lower precision.

Floating-Point Arithmetic
#

Align exponents first, then add — this can cause large values to “eat” small ones.

Use an accumulation algorithm like the following:

image-20250222151821829

Comparison pitfall: 0.1 + 0.2 != 0.3 (from non-terminating binary fractions).

[!NOTE]

Another point worth noting is round-to-nearest-even (banker’s rounding) on type conversions, which helps reduce accumulated rounding error.

Homework
#

At this stage, do the three CSAPP labs and work through the end-of-chapter exercises for CSAPP Ch. 2 and 3 (I skipped writing up Ch. 2; I only cover Ch. 3 here).

1. datalab

2. bomblab (gdb usage — quite hard; I suggest reading more, doing exercises, and understanding before diving in)

Common gdb commands (from https://arthals.ink/blog/bomb-lab as a reference blog)

p $rax  # 打印寄存器 rax 的值
p $rsp  # 打印栈指针的值
p/x $rsp  # 打印栈指针的值,以十六进制显示
p/d $rsp  # 打印栈指针的值,以十进制显示

x/2x $rsp  # 以十六进制格式查看栈指针 %rsp 指向的内存位置 M[%rsp] 开始的两个单位。
x/2d $rsp # 以十进制格式查看栈指针 %rsp 指向的内存位置 M[%rsp] 开始的两个单位。
x/2c $rsp # 以字符格式查看栈指针 %rsp 指向的内存位置 M[%rsp] 开始的两个单位。
x/s $rsp # 把栈指针指向的内存位置 M[%rsp] 当作 C 风格字符串来查看。

x/b $rsp # 检查栈指针指向的内存位置 M[%rsp] 开始的 1 字节。
x/h $rsp # 检查栈指针指向的内存位置 M[%rsp] 开始的 2 字节(半字)。
x/w $rsp # 检查栈指针指向的内存位置 M[%rsp] 开始的 4 字节(字)。
x/g $rsp # 检查栈指针指向的内存位置 M[%rsp] 开始的 8 字节(双字)。

info registers  # 打印所有寄存器的值
info breakpoints  # 打印所有断点的信息

delete breakpoints 1  # 删除第一个断点,可以简写为 d 1

3. attacklab (simulated attack)

[!NOTE]

The above takes a long time — haste makes waste. If you get stuck, look at recommended blogs on CSDIY.

Linking — A Brief Overview
#

Static Linking
#

Think of it as how multi-file programs are combined to run as one.

image-20250225152305222

Analyzing Relocatable Object Files
#

After assembling a single file, the .o is a relocatable object file.

(Using the two programs below)

readelf -a main.o	//分析elf文件内容

hexdump  -C main.o	//直接查看文件的二进制信息

image-20250225155226314

Symbol Table Information
#

image-20250225191350912

Weak and Strong Symbols
#

image-20250225192034009

Executable Files
#

image-20250225192753470

Inspect the Program Headers of an executable (how is it loaded and run?)

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x001020 0x00800020 0x00800020 0x00198 0x00198 R E 0x1000
  LOAD           0x002000 0x00801000 0x00801000 0x00038 0x00050 RW  0x1000
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x10

 Section to Segment mapping:
  Segment Sections...
   00     .text .rodata 
   01     .data .bss 
   02     

The Static Linking Process
#

image-20250225194125819

/* Simple linker script for os user-level programs.
   See the GNU ld 'info' manual ("info ld") to learn the syntax. */
//一个简单的linker脚本
OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386")	//输出格式
OUTPUT_ARCH(i386)	//架构类型
ENTRY(main)	//程序的入口点(main函数)

SECTIONS {
    /* Load programs at this address: "." means the current address */
    //在这个地址对程序进行加载
    . = 0x800020;
	
    //以下都是把每个目标文件中的相同的段合并到新的段
    .text : {
        *(.text .stub .text.* .gnu.linkonce.t.*)
    }
    
    PROVIDE(etext = .); /* Define the 'etext' symbol to this value */
    
    .rodata : {
        *(.rodata .rodata.* .gnu.linkonce.r.*)
    }
    
    /* Adjust the address for the data segment to the next page */
    //转页进行存储,以上的页就可以设置成ro的一个页
    . = ALIGN(0x1000);
    
    .data : {
        *(.data)
    }
    
    //记录下来,把.bss段设置成0
    PROVIDE(edata = .);
    
    .bss : {
        *(.bss)
    }
    
    PROVIDE(end = .);
    
    /DISCARD/ : {
        *(.eh_frame .note.GNU-stack .comment)
    }

}

Relocation Information
#

image-20250225200024731