Running the openEuler system on the FT-D2000 processor. Starting from scratch, sharing implementation methods and insights~
Step 1: Deploying a QEMU Virtual Machine on Windows to Run openEuler aarch64 Image
Preparation:
Directory Structure (very important—avoids half the common pitfalls): Create a dedicated workspace directory on a non-system drive, with no spaces or Chinese characters in the path. Store all files (images, firmware, .bat scripts) in this directory. For example, create a folder at D:\ZNPT.
-
Download the openEuler aarch64 virtual machine image from the URL: dl-cdn.openeuler.openatom.cn/openEuler-24.03-LTS-SP3/virtual_machine_img/aarch64/. Extract the image file.
-
Download and install QEMU for Windows from: https://qemu.weilnetz.de/w64/2026/. During installation, change the install path to
D:\ZNPT\qemu. Manually add the path to the system environment variables: -
Go to System Properties → Advanced → Environment Variables → System Variables → Path → Edit → Add →
D:\ZNPT\qemu -
Create a differential snapshot disk. Do not run the official image directly as a system disk; instead, use a backing file layer for differential storage:
cd D:\ZNPT
qemu-img create -f qcow2 -b openEuler-24.03-LTS-SP3-aarch64.qcow2 -F qcow2 my_vm.qcow2 40G
qemu-img info my_vm.qcow2
- Create a startup script (the most practical approach on Windows). In
D:\ZNPT, create a new text file and rename it tostart.bat, with the following content:
@echo off
chcp 65001 >nul
title openEuler-24.03 Launcher
set QEMU_DIR=D:\ZNPT\qemu
set IMG_DIR=D:\ZNPT
set UEFI_FD=%QEMU_DIR%\share\edk2-aarch64-code.fd
:: Kill any lingering QEMU processes before starting to prevent disk lock
taskkill /F /IM qemu-system-aarch64.exe >nul 2>&1
:: Start QEMU
start "" "%QEMU_DIR%\qemu-system-aarch64.exe" ^
-M virt ^
-cpu cortex-a72 ^
-smp 4 ^
-m 4G ^
-accel tcg,thread=multi ^
-bios "%UEFI_FD%" ^
-drive file="%IMG_DIR%\my_vm.qcow2",if=virtio,format=qcow2 ^
-device virtio-gpu-pci ^
-display gtk,gl=off ^
-device nec-usb-xhci ^
-device usb-kbd ^
-device usb-tablet ^
-netdev user,id=n0,hostfwd=tcp::2222-:22,hostfwd=tcp::8080-:8080 ^
-device virtio-net-pci,netdev=n0 ^
-rtc base=localtime,clock=vm
exit
- After booting into the system, the default login credentials are:
- Username:
root - Password:
openEuler12#$
- Username:
Welcome to the community, and we look forward to seeing your continued progress on the FT-D2000! Have you successfully booted and logged into openEuler in QEMU already? If you’re encountering issues, please share the specific error messages, and we can discuss them together.
Let me add a note on the scope of this experiment: this script uses QEMU’s generic virt platform and TCG emulation, which is ideal for getting familiar with the aarch64 system and testing general software. However, it does not emulate the specific hardware of the FT-D2000, so it cannot be used to assess firmware or driver compatibility on real hardware. If this is a preparatory step for later deployment on actual devices, it’s helpful to clearly state that in your post.
Also, the code may have become distorted during editing or pasting. For example, IMG_DIR and cortex-a72 in the original have become I MG_DIR and cor tex-a72 with extra spaces in the text—these spaces will break command execution. We recommend copying the code directly from the actual .bat file and pasting it as plain text into a Markdown code block to avoid embedded rich text formatting.
Here’s how to write a code block correctly: include the three backticks at the beginning and end, and specify bat to indicate the language:
```bat
@echo off
set "IMG_DIR=D:\ZNPT"
```
If you’re not familiar with Markdown, you can refer to this Chinese tutorial on Fenced Code Blocks and Syntax Highlighting. Simply learning how to post code blocks is already very useful—there’s no need to master all syntax at once. Before posting, preview your message and check it again in a text editor to catch any formatting changes.
One more suggestion: consider removing the automatic cleanup step that runs taskkill /F /IM qemu-system-aarch64.exe before starting the VM. This could accidentally terminate other running instances of the same VM process, potentially causing data loss if unsaved. Instead, check first whether another VM is already using the disk image, and if so, shut it down cleanly.
If port forwarding is intended for local access only, you can explicitly bind to the loopback address, for example: hostfwd=tcp:127.0.0.1:2222-:22. As for the initial image password, please change it after your first login.
This post is meant to be a stepping-stone for ongoing updates—there’s no need to turn it into a complete tutorial from the start. Simply share your current progress, the actual commands you’ve used, and any issues you’ve encountered. That way, others can easily join in and follow along.
Step 2: Set up convenient operations in VScode for writing code and then uploading it to run on QEMU virtual machine
- Start the SSH service and enable it to start automatically on boot:
systemctl start sshd
systemctl enable sshd
-
Download the VScode cross-compilation toolchain from https://gitlab.arm.com/tooling/gnu-toolchains-for-arm/-/tree/releases/15.3.rel1?ref_type=heads. Locate the file
arm-gnu-toolchain-15.3.rel1-mingw-w64-x86_64-aarch64-none-linux-gnu.zip. Extract it to the pathD:/ZNPT/tool/arm-gnu-toolchain-15.3.rel1-mingw-w64-x86_64-aarch64-none-linux-gnu/. -
Create a project folder, for example
D:\workspace\oe_vm. Inside it, create a.vscodefolder to store the automatic build scripttasks.json, and create asrcfolder to hold themain.cfile. -
Write the automatic build script
tasks.jsonas follows:
{
"version": "2.0.0",
"tasks": [
{
"label": "Cross-compile openEuler (multi-file)",
"type": "shell",
// 1. Modify this: Replace with your actual extracted absolute path
"command": "D:/ZNPT/tool/arm-gnu-toolchain-15.3.rel1-mingw-w64-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gcc.exe",
"args": [
"-o",
"hello_world_app", // Name of the compiled output file
// 2. Compile all .c files from different directories (modify this to support multiple directories)
"ctu_app/*.c",
"common_lib/*.c",
"tc_mgmt_lib/*.c",
"tm_mgmt_lib/*.c",
"device_mgmt_lib/*.c",
// 3. Specify include directories (modify this to prevent #include "xxx.h" errors)
"-I", "src",
"-I", "common_lib",
"-I", "tc_mgmt_lib",
"-I", "tm_mgmt_lib",
"-I", "device_mgmt_lib",
// 4. Compilation optimization and static linking (keep unchanged)
"-static",
"-Wall"
],
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": ["$gcc"]
}
]
}
- Create the test demo file
main.cas follows:
/*
* @Author: zouyi zouliyu@live.cn
* @Date: 2026-09-21 12:02:42
* @LastEditors: zouyi zouliyu@live.cn
* @LastEditTime: 2026-09-22 16:31:39
* @FilePath: D:\openEuler_claude_test0918\src\main.c
* @Description: This is the default setting. Please set `customMade`. Open koroFileHeader to view configuration for settings: https://github.com/OBKoro1/koro1FileHeader/wiki/Configuration
*/
#include <stdio.h>
#include <unistd.h>
int main(void) {
while (1) {
printf("hello world\n");
fflush(stdout); // Ensure output is immediately flushed to avoid buffering issues
sleep(1); // Execute every 1 second
}
return 0;
}
- Create a script to automatically upload the compiled binary to the QEMU virtual machine’s openEuler system. In the project directory, create a file named
upload.ps1with the following content:
# scp -P 2222 .\hello_world_app root@127.0.0.1:/root/
#
-
Compile by pressing
Ctrl+Shift+B. You should see the generated output filehello_world_app. -
Upload: Open the terminal in VScode and run
.\upload.ps1. You should now see thehello_world_appbinary in the QEMU virtual machine’srootdirectory. Then run./hello_world_appto execute it.
I’ve successfully booted and logged into openEuler within QEMU, and I’m able to cross-compile a few processes to run on the openEuler system inside the QEMU virtual machine. Thanks ~
The code formatting on the second and fourth floors both need to be fixed:
- On the second floor, commands and startup scripts are not enclosed in code blocks, and variable names and filenames contain stray spaces, such as
I MG_DIRormy_vm.qc ow2. - On the fourth floor, PowerShell comments are rendered as large headers, some variables are mistakenly parsed as mathematical expressions, and the
tasks.jsonfile is split across code blocks.
As it stands, readers cannot correctly copy the complete code from the page; even if manually stitched together, the result may not match the original file.
Just to confirm: were these two sections directly pasted into the forum editor, or published via another tool?
- If using the forum editor, please paste the code from the original files as plain text, enclose each code block completely, and check the preview before submission.
- If using an automated publishing tool, verify the expected body format and preview the Markdown using a tool like VS Code prior to publishing.
In both cases, you must review the post after publishing. The rendering behavior between the forum and local Markdown tools may differ—local preview looking correct doesn’t guarantee the final display will be correct.
Proper formatting is part of technical content: it ensures code can be accurately read and copied, not just that the page looks presentable. Please directly edit the second and fourth floors, restore the code from the original files, and make your documentation usable for other readers.
Posts with severe readability issues that remain uncorrected may be temporarily hidden by moderators until fixed. We hope to resolve this through editing so you don’t lose the value of your shared content.
Step 3: Set up a network debugging assistant to send and receive data over the network
-
Download a network debugging assistant, NetAssist.exe (you can find it online);
-
Write the network driver code by creating net_device.c and net_device.h files as shown below.
net_device.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "net_device.h"
#define NET_BUF_SIZE 1024
static int g_listen_fd = -1;
static int g_client_fd = -1;
/* ---------- Initialization ---------- */
int NetAppInit(unsigned short port)
{
signal(SIGPIPE, SIG_IGN);
g_listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (g_listen_fd < 0) { perror("socket"); return -1; }
int opt = 1;
setsockopt(g_listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(port);
if (bind(g_listen_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind"); close(g_listen_fd); g_listen_fd = -1; return -1;
}
if (listen(g_listen_fd, 4) < 0) {
perror("listen"); close(g_listen_fd); g_listen_fd = -1; return -1;
}
fcntl(g_listen_fd, F_SETFL, fcntl(g_listen_fd, F_GETFL, 0) | O_NONBLOCK);
printf("[NET] listening on 0.0.0.0:%u\r\n", port);
fflush(stdout);
return 0;
}
/* ---------- Receive ---------- */
int NetAppRecv(char *buf, int buf_size)
{
if (buf == NULL || buf_size <= 0) return 0;
/* 1. No client connected → try to accept */
if (g_client_fd < 0) {
g_client_fd = accept(g_listen_fd, NULL, NULL);
if (g_client_fd >= 0) {
fcntl(g_client_fd, F_SETFL,
fcntl(g_client_fd, F_GETFL, 0) | O_NONBLOCK);
printf("[NET] client connected\r\n");
fflush(stdout);
}
return 0;
}
/* 2. Client connected → receive data */
int n = recv(g_client_fd, buf, buf_size - 1, 0);
if (n > 0) {
return n;
} else if (n == 0) {
printf("[NET] client closed\r\n");
fflush(stdout);
close(g_client_fd);
g_client_fd = -1;
return -1;
} else {
if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) {
perror("recv");
close(g_client_fd);
g_client_fd = -1;
return -1;
}
return 0;
}
}
/* ---------- Send ---------- */
int NetAppSend(const char *buf, int len)
{
if (g_client_fd < 0 || buf == NULL || len <= 0) return -1;
int m = send(g_client_fd, buf, len, 0);
if (m < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) {
perror("send");
close(g_client_fd);
g_client_fd = -1;
}
return m;
}
/* ---------- Close ---------- */
void NetAppClose(void)
{
if (g_client_fd >= 0) { close(g_client_fd); g_client_fd = -1; }
if (g_listen_fd >= 0) { close(g_listen_fd); g_listen_fd = -1; }
}
net_device.h
#ifndef SRC_NET_DEVICE_H_
#define SRC_NET_DEVICE_H_
int NetAppInit(unsigned short port);
/* Receive data: place received data into buf, return actual number of bytes received
* Returns 0: no data available or no client connected
* Returns -1: connection abnormal disconnect */
int NetAppRecv(char *buf, int buf_size);
/* Send data: return actual number of bytes sent, -1 indicates no connection or failure */
int NetAppSend(const char *buf, int len);
void NetAppClose(void);
#endif
- In the main.c file, initialize the network and call the receive and send functions.
/*
* @Author: zouyi zouliyu@live.cn
* @Date: 2026-09-21 12:02:42
* @LastEditors: zouyi zouliyu@live.cn
* @LastEditTime: 2026-09-22 16:31:39
* @FilePath: \openeuler_claude_test0918\src\main.c
* @Description: This is the default setting. Please set `customMade`, open koroFileHeader to view configuration and set it: https://github.com/OBKoro1/koro1FileHeader/wiki/Configuration
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "net_device.h"
int main(void) {
char txbuf[1100] = {1,2,3,4,5,6,7,8,9};
char rxbuf[1100];
NetAppInit(8080);
while (1) {
/* 1. Receive */
int n = NetAppRecv(rxbuf, sizeof(rxbuf));
if (n > 0) {
printf("rec_len ================= %d \r\n", n);
NetAppSend(rxbuf, n);
}
/* 2. Send */
NetAppSend(txbuf, 8);
printf("hello world\n");
fflush(stdout); // Ensure output is immediately flushed to avoid buffering issues
sleep(1); // Run every 1 second
}
return 0;
}
-
Compile the code by pressing Ctrl+Shift+B. You should see the generated target binary named hello_world_app.
-
Upload the file: Open the terminal in VS Code and run .\upload.ps1. You’ll see the hello_world_app binary now present in the root folder of the QEMU virtual machine. Run it using ./hello_world_app.
-
Open the network debugging assistant, select TCP Client, enter 127.0.0.1 as the remote IP address and set the port to 8080. Click Connect — you should see the connection succeed and data flow displayed in the assistant.
代码格式修改了,谢谢提醒~
Thank you for following up on the revisions. In this update, a lot of code was placed inside quote blocks. However, quote blocks are different from code blocks—content within them is still processed as Markdown, so line breaks, comments, and symbols are still displayed incorrectly.
I’ve already reorganized the formatting on the second, fourth, and seventh floors: each code snippet is now fully wrapped in code blocks, and filenames, paths, and commands in the main text have been wrapped in inline code formatting. You can open your editor and refer to the current style as a reference.
This revision only focused on formatting; no code was run, verified, or rewritten. The original text that was already corrupted remains unchanged. For example, the header in the fourth-floor main.c file still contains messy content like @AuthorAuthor—please cross-check and fix it against your local original file. Code appearing correctly does not mean the content has been fully restored.
Also, the language in the fourth-floor post was incorrectly detected as English, causing the page to display a machine-translated version in Chinese. I’ve corrected the language tag to Simplified Chinese.
One more thing: could you confirm where this content was originally copied from? Was it pasted through the forum editor, or published via another tool? Knowing this will help identify at which step the formatting deviated.
For future updates, please continue organizing content using code blocks and inline code. Always check the preview before submitting, and review the post after publishing to ensure readers can accurately read and copy the content.
感谢up的修改。这些代码是从VSCode里面拷贝出来的。格式我再改改,谢谢了~
