Everyone knows that we can use C to write programs that control hardware, but do you know how it actually works?
A netizen explained that in practice, the C language runs as assembly instructions. The compiler translates C code into assembly, which the CPU then executes directly. So the real question becomes: how do these assembly instructions interact with hardware?
If we're working within an x86 environment, there are generally two main methods to interface with hardware:
1. **Memory-Mapped I/O (MMIO)**
This involves writing data to a specific memory address space. Hardware devices map their internal registers (which act like interfaces for controlling the device) to certain physical memory addresses. By reading from or writing to those addresses using assembly or even C, you can effectively control the hardware.
For example, in a Windows XP environment (or a virtual machine), you can manipulate video memory directly using assembly:
```
MOV AX, B800
MOV ES, AX
XOR DI, DI
MOV CX, 0800
MOV AX, 5555
REPZ STOSB
```
In this case, the video memory is mapped to a physical address, and by accessing it, you can change what’s displayed on the screen. You can check this in the Device Manager under "Resources" — many devices have a "memory range" parameter, indicating that they can be controlled via memory access.
2. **Port-Mapped I/O (I/O Ports)**
On x86 systems, there are special instructions like `IN` and `OUT` used to read from or write to I/O ports. These are unique to the x86 architecture and allow direct communication with hardware devices through dedicated port addresses.
While MMIO can be implemented in C, such as:
```c
char *ptr = (char *)0xB8000;
for (int i = 0; i < 0x800; i++) {
ptr[i] = 0x55;
}
```
The `IN` and `OUT` instructions don’t have a direct C syntax equivalent, so they must be encapsulated using inline assembly.
So why is it difficult to control hardware directly in C? Most of the time, the code runs in protected mode, where direct access to physical memory is restricted. C typically uses virtual addresses, not physical ones.
In Windows, accessing physical memory requires kernel-mode programming, such as writing a driver. This is because user-mode applications don't have direct access to physical memory.
Understanding the difference between physical and virtual addresses is key. For instance, the original 8086 CPU reserved a specific physical address range (000A0000–000BFFFF) for video memory. This area is still reserved today, regardless of whether the system is 32-bit or 64-bit, and whether it's in real mode or protected mode.
But why can't we just use this memory now? Because modern software runs in protected mode, and all memory accesses are virtual. Even in the command prompt, the addresses you see are virtual. Although 32-bit Windows XP could use the `debug` command to write to 000B8000, it was still virtual memory.
To access this video memory directly, you'd need to write a simple operating system that maps the entire 4GB address space to physical memory, similar to DOS. Then, writing to 000B8000 would display text on the screen.
How does the graphics card manage so much memory? It maps a portion of its own memory to physical addresses, allowing the CPU to access it as if it were regular memory. However, only part of the video memory may be mapped at a time, and the graphics card controls which parts are accessible via its registers.
Another interesting point: in a virtual machine, if you find a high-address memory region and write a program that can access physical memory (like a driver), you can read from and write to the video memory, and even take screenshots. The content will match what's stored in the video memory.
Another netizen, awayisblue, added that understanding how hardware works starts with knowing what a microcontroller is. Take the SMC8 chip, for example. It has a CPU, memory, and registers. Each pin corresponds to a register, and writing data to it changes the voltage level of that pin. For instance, writing 01100001 causes the corresponding pins to output low, high, high, low, etc.
The CPU can execute instructions that manipulate memory, and since registers are memory units, the CPU can control the pin levels. When connected to a display, the MCU can write data to the display's memory, which then shows characters based on ASCII values.
This is how drivers work. A driver acts as a middle layer between the software and hardware. It abstracts the hardware details and provides functions like `clear()`, `write()`, or `move_cursor()`. These functions manipulate the appropriate registers, which in turn control the pin levels and the display.
So, in C, you can directly operate hardware by calling these driver functions. The driver itself can be written in C or even in assembly for performance. The key idea is that C operates on memory, and some memory addresses correspond to hardware registers.
Another netizen, Chow Anod, added that on the language level, C can only operate on memory addresses. While C supports the `register` keyword, it can't specify a particular register. So the only way to interact with hardware is through pointers. For example:
```c
uint32_t *p = SCREEN_ADDR;
*p = RGBA(0xff, 0xff, 0xff, 0xff);
```
This kind of code is common in embedded systems. But to get the correct address, you need to refer to the device's specification.
Finally, someone else mentioned that when you type on a keyboard, each character is an ASCII code, which is essentially a voltage level. The computer doesn't understand what it's displaying — it just knows how to render it. Compilation converts these ordered voltages into another set of voltages, and burning firmware transfers them to the microcontroller’s ROM, which then controls the hardware.
In short, everything boils down to voltage levels. Software is just a representation of these levels, and there's no real "conversion" — it's all about managing the flow of electricity through circuits.
JIANGSU BEST ENERGY CO.,LTD , https://www.bestenergy-group.com