How to Directly Control Hardware with C Language? Pointers, Memory, and Registers

Hello everyone, I am Xiao Feng Ge. This will be a series of articles related to the C language. In the previous article, we learned why we should use C language to develop operating systems. In this article, we will look at how C language controls hardware.

The Design Philosophy of C Language

The design philosophy of C language can be summarized as “Trust the Programmer“.

Unlike many modern programming languages, C language imposes very few restrictions on the behavior of programmers, it assumes that the programmer knows what they are doing.

How to Directly Control Hardware with C Language? Pointers, Memory, and Registers

Therefore, C language is actually a language that demands a lot from programmers.

Decades have passed, and despite the emergence of many new programming languages, C language remains the dominant language for operating system and device driver development. This is not a coincidence, but rather a perfect fit between the characteristics of C language and the needs of system programming, one of the key factors being that C language can achieve direct control over hardware.

How is this achieved?

CPU Registers and Memory

Before understanding how C language directly controls hardware, we need to first understand two core components of computer hardware: CPU registers and physical memory.

How to Directly Control Hardware with C Language? Pointers, Memory, and Registers

These two components form the basis for the computer to execute instructions and store data, and they are the key interfaces through which C language can achieve low-level control.

CPU registers are high-speed, very small capacity storage units inside the processor, they are the direct objects of operation when the CPU executes instructions.

One can think of registers as the “workbench” of the CPU, where all calculations and data processing must take place.

Whether loading instructions, performing calculations, or accessing memory, all involve the participation of registers.

How to Directly Control Hardware with C Language? Pointers, Memory, and Registers

The main functions of registers include:

  • Storing temporary data during instruction execution
  • Saving memory addresses for memory access
  • Recording the working state of the CPU (e.g., whether the result of a calculation is zero, whether a carry has occurred, etc.)
  • Controlling the execution flow of the program (e.g., the address of the next instruction)

Next, let’s look at physical memory.

Physical memory, usually referring to the main storage (RAM, Random Access Memory), is the primary storage device used by the computer to store program code, data, and runtime information. If we compare registers to the “workbench” of the CPU, then physical memory is the “large warehouse” of the computer, storing all the data needed for program execution.

The main functions of physical memory include:

  • Storing the currently executing program code
  • Saving data during program execution (e.g., variables, arrays, structures, etc.)
  • Maintaining the running state of the program (e.g., function call stack, heap memory, etc.)

How to Directly Control Hardware with C Language? Pointers, Memory, and Registers

When we say that C language can directly control hardware, it is more reflected in the control over registers and memory.

The Tool for C Language to Control Registers: Inline Assembly

Inline assembly allows embedding assembly instructions directly in C code, enabling low-level operations that cannot be expressed in C syntax:

  • Directly read and write specific CPU registers: Accessing specific registers like EAX, CR0, etc.

  • Executing privileged instructions: Operations that require special permissions, such as modifying page tables or changing processor modes.

  • Optimizing extreme performance: Using hand-optimized assembly code on performance-critical paths, etc.

The GCC compiler provides powerful support for inline assembly, with the basic syntax as follows:

// Store the value of EAX register into result variable
asm volatile ("movl %%eax, %0" : "=r"(result) : );

// Load the value of value variable into EAX register
asm volatile ("movl %1, %%eax" : : "r"(value));

// Perform a system call
asm volatile ("int $0x80" : : "a"(syscall_num), "b"(arg1));

Inline assembly is the most direct manifestation of C language penetrating its own abstraction to reach hardware.

<span>asm</span> instructions in the block can directly manipulate physical registers (EAX, EBX, etc.) or specific memory addresses, bypassing the variable abstraction of C language and the compiler’s register allocation mechanism.

The operating system kernel extensively uses inline assembly to implement:

  • Context switching (saving and restoring register states)
  • Switching processor privilege levels
  • Page table operations
  • Interrupt handling
  • Atomic operations

Although inline assembly is powerful, it also brings risks and challenges:

  • Breaking portability
  • Increasing code complexity
  • Potentially introducing hard-to-debug errors

Therefore, inline assembly is usually regarded as a “last resort” and is only used when absolutely necessary, often encapsulated in macros or functions to improve maintainability.

The Tool for C Language to Control Memory: Pointers

Before understanding pointers in C language, we must understand the essence of variables.

When we declare a variable in C language (e.g., <span>int a; char c;</span>), what are we actually doing?

Essentially, we are requesting a block of memory from the compiler and giving it a name and type. The compiler allocates an appropriate amount of memory based on the variable’s type and records the starting address of this memory.

For example, when we declare <span>int a;</span>, the compiler will:

  • Allocate 4 bytes (on most modern systems) in the appropriate memory area (usually the stack)
  • Associate this memory with the identifier <span>a</span>
  • Record that this memory should be interpreted as an integer type

How to Directly Control Hardware with C Language? Pointers, Memory, and Registers

The variable name is a programmer-friendly identifier that only exists in the source code and compilation stage. Once the program is compiled into machine code, the variable name is replaced with a specific memory address. When the CPU executes instructions, it does not know the existence of variable names; it only knows to operate on data at specific memory addresses.

Essentially, a pointer is also a variable, but its value is the memory address of another variable; in other words, a pointer “points to” a location in memory.

For example, <span>int *p;</span> declares a pointer to an integer, which tells the compiler that the value of <span>p</span> is a memory address, and the data stored at that address should be interpreted as an integer.

How to Directly Control Hardware with C Language? Pointers, Memory, and Registers

Since pointers are also variables, they can be subjected to regular operations like addition and subtraction, thus allowing C language to directly manipulate memory addresses and achieve precise control over hardware.

It must be noted that in user mode, although pointers can be used, they operate on virtual memory, which is still not true physical memory. However, in kernel mode, it is different; the operating system can directly manipulate physical memory.

It is through pointers that C language establishes a bridge between high-level language abstraction and low-level hardware operations.

The low-level control capabilities of C language make it an ideal tool to meet these challenges, although this also means that programmers need to take on more responsibility to ensure the correctness and safety of the code.

In short, when you use C language for system programming, you need to be very clear about what you are doing

Finally, I recommend my column “In-Depth Understanding of Operating Systems”, which has been upgraded to the second edition with 600+ exquisite hand-drawn illustrations and 87 structured lessons, specifically designed to help you understand operating systems thoroughly. If you want to completely grasp operating systems, then this column is prepared for you:

How to Directly Control Hardware with C Language? Pointers, Memory, and Registers

Leave a Comment