蓝色天空

电子,图形,编程,游戏等分享和记录

0%

问题

arm架构的服务器上,qemu不支持qxl显示,只支持virtio-gpu显示

解决办法

  1. 修改 UEFI 代码

参考: https://www.kraxel.org/blog/2022/05/edk2-virt-quickstart/
源码: https://github.com/tianocore/edk2.git
补丁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
diff --git a/ArmVirtPkg/ArmVirtQemu.dsc b/ArmVirtPkg/ArmVirtQemu.dsc
index 7e2ff33ad1..abb7828178 100644
--- a/ArmVirtPkg/ArmVirtQemu.dsc
+++ b/ArmVirtPkg/ArmVirtQemu.dsc
@@ -543,6 +543,7 @@
OvmfPkg/QemuRamfbDxe/QemuRamfbDxe.inf
OvmfPkg/VirtioGpuDxe/VirtioGpu.inf
OvmfPkg/PlatformDxe/Platform.inf
+ OvmfPkg/QemuVideoDxe/QemuVideoDxe.inf

#
# USB Support
diff --git a/ArmVirtPkg/ArmVirtQemu.fdf b/ArmVirtPkg/ArmVirtQemu.fdf
index 764f652afd..7c3c788853 100644
--- a/ArmVirtPkg/ArmVirtQemu.fdf
+++ b/ArmVirtPkg/ArmVirtQemu.fdf
@@ -110,6 +110,7 @@ READ_LOCK_STATUS = TRUE
INF ArmVirtPkg/MemoryInitPei/MemoryInitPeim.inf
INF ArmPkg/Drivers/CpuPei/CpuPei.inf
INF MdeModulePkg/Core/DxeIplPeim/DxeIpl.inf
+ INF OvmfPkg/QemuVideoDxe/QemuVideoDxe.inf

!if $(TPM2_ENABLE) == TRUE
INF MdeModulePkg/Universal/PCD/Pei/Pcd.inf
  1. 修改 qemu 代码

源码: https://github.com/qemu/qemu.git
补丁

1
2
3
4
5
6
7
8
9
10
11
12
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 218b454e97..266b7820be 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -33,6 +33,7 @@ config ARM_VIRT
select VIRTIO_MEM_SUPPORTED
select ACPI_CXL
select ACPI_HMAT
+ select QXL

config CHEETAH
bool
  1. 修改linux内核 qxl 驱动代码

源码: https://github.com/torvalds/linux.git
补丁: 省略

一般一个设备只能装一个系统,一个系统上同时只能跑一个图形服务。
如果想要跑多个图形服务,可以考虑使用容器。

生成容器系统

  1. 安装 debootstrap
1
sudo apt install debootstrap fakeroot docker.io
  1. 生成容器系统
1
fakeroot debootstrap buster root https://mirrors.ustc.edu.cn/debian/
  1. 打包成容器
1
tar --xattrs -c -C root . | docker import -c 'ENTRYPOINT ["/init"]' - debian-buster

容器系统中添加图形服务

  1. 准备 debian buster 仓库源 sources.list
1
2
3
4
5
6
7
8
9
10
11
deb https://mirrors.ustc.edu.cn/debian/ buster main contrib non-free
deb-src https://mirrors.ustc.edu.cn/debian/ buster main contrib non-free

deb https://mirrors.ustc.edu.cn/debian/ buster-updates main contrib non-free
deb-src https://mirrors.ustc.edu.cn/debian/ buster-updates main contrib non-free

deb https://mirrors.ustc.edu.cn/debian/ buster-backports main contrib non-free
deb-src https://mirrors.ustc.edu.cn/debian/ buster-backports main contrib non-free

deb https://mirrors.ustc.edu.cn/debian-security/ buster/updates main contrib non-free
deb-src https://mirrors.ustc.edu.cn/debian-security/ buster/updates main contrib non-free
  1. 准备 init.sh 文件
1
2
3
4
5
6
7
#!/bin/bash

Xvfb :99 -ac -listen tcp -screen 0 1280x800x24 &
sleep 3
/usr/bin/fluxbox -display :99 -screen 0 &
sleep 3
x11vnc -display :99 -forever -passwd ldj
  1. 编写 Dockerfile 文件
1
2
3
4
5
6
FROM debian-buster
COPY sources.list /etc/apt/sources.list
COPY init.sh /etc/init.sh
RUN apt update; apt install -y xvfb x11vnc fluxbox firefox-esr
EXPOSE 5900
CMD ["/etc/init.sh"]
  1. 生成 docker 镜像
1
docker build -t debian-buster-firefox .

运行容器系统

1
sudo docker run -d -p 5900:5900 debian-buster-firefox:latest

打开vnc客户端,然后连接 localhost:5900 即可看到容器系统中的图形应用 firefox

什么是 DMI

DMI(Desktop Management Interface,DMI)就是帮助收集电脑系统信息的管理系统,DMI信息的收集必须在严格遵照SMBIOS规范的前提下进行。SMBIOS(System Management BIOS)是主板或系统制造者以标准格式显示产品管理信息所需遵循的统一规范。SMBIOS和DMI是由行业指导机构Desktop Management Task Force(DMTF)起草的开放性的技术标准,其中DMI设计适用于任何的平台和操作系统。

DMI充当了管理工具和系统层之间接口的角色。它建立了标准的可管理系统更加方便了电脑厂商和用户对系统的了解。DMI的主要组成部分是Management Information Format(MIF)数据库。这个数据库包括了所有有关电脑系统和配件的信息。通过DMI,用户可以获取序列号、电脑厂商、串口信息以及其它系统配件信息。

使用dmidecode获取DMI信息

dmidecode命令 可以让你在Linux系统下获取有关硬件方面的信息。dmidecode的作用是将DMI数据库中的信息解码,以可读的文本方式显示。由于DMI信息可以人为修改,因此里面的信息不一定是系统准确的信息。dmidecode遵循SMBIOS/DMI标准,其输出的信息包括BIOS、系统、主板、处理器、内存、缓存等等。

安装 dmidecode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# sudo apt install dmidecode
# dmidecode -h
Usage: dmidecode [OPTIONS]
Options are:
-d, --dev-mem FILE Read memory from device FILE (default: /dev/mem)
-h, --help Display this help text and exit
-q, --quiet Less verbose output
-s, --string KEYWORD Only display the value of the given DMI string
-t, --type TYPE Only display the entries of given type
-H, --handle HANDLE Only display the entry of given handle
-u, --dump Do not decode the entries
--dump-bin FILE Dump the DMI data to a binary file
--from-dump FILE Read the DMI data from a binary file
--no-sysfs Do not attempt to read DMI data from sysfs files
--oem-string N Only display the value of the given OEM string
-V, --version Display the version and exit

使用 dmidecode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# sudo dmidecode
SMBIOS 2.5 present.

Handle 0x0001, DMI type 4, 40 bytes
Processor Information
Socket Designation: Node 1 Socket 1
Type: Central Processor
Family: Xeon MP
Manufacturer: Intel(R) Corporation
id: C2 06 02 00 FF FB EB BF
Signature: Type 0, Family 6, Model 44, Stepping 2
Flags:
FPU (Floating-point unit on-chip)
VME (Virtual mode extension)
DE (Debugging extension)
PSE (Page size extension)
TSC (time stamp counter)
MSR (Model specific registers)
PAE (Physical address extension)
MCE (Machine check exception)
CX8 (CMPXCHG8 instruction supported)
APIC (On-chip APIC hardware supported)
SEP (Fast system call)
MTRR (Memory type range registers)
PGE (Page global enable)
MCA (Machine check architecture)
CMOV (Conditional move instruction supported)
PAT (Page attribute table)
PSE-36 (36-bit page size extension)
CLFSH (CLFLUSH instruction supported)
DS (Debug store)
ACPI (ACPI supported)
MMX (MMX technology supported)
FXSR (FXSAVE and FXSTOR instructions supported)
SSE (Streaming SIMD extensions)
SSE2 (Streaming SIMD extensions 2)
ss (Self-snoop)
HTT (Multi-threading)
TM (Thermal monitor supported)
PBE (Pending break enabled)
Version: Intel(R) Xeon(R) CPU E5620 @ 2.40GHz
Voltage: 1.2 V
External Clock: 5866 MHz
Max Speed: 4400 MHz
Current Speed: 2400 MHz
Status: Populated, Enabled
Upgrade: ZIF Socket
L1 Cache Handle: 0x0002
L2 Cache Handle: 0x0003
L3 Cache Handle: 0x0004
Serial Number: Not Specified
Asset Tag: Not Specified
Part Number: Not Specified
Core Count: 4
Core Enabled: 4
Thread Count: 8
Characteristics:
64-bit capable

Handle 0x0055, DMI type 4, 40 bytes
Processor Information
Socket Designation: Node 1 Socket 2
Type: Central Processor
Family: Xeon MP
Manufacturer: Not Specified
ID: 00 00 00 00 00 00 00 00
Signature: Type 0, Family 0, Model 0, Stepping 0
Flags: None
Version: Not Specified
Voltage: 1.2 V
External Clock: 5866 MHz
Max Speed: 4400 MHz
Current Speed: Unknown
Status: Unpopulated
Upgrade: ZIF Socket
L1 Cache Handle: Not Provided
L2 Cache Handle: Not Provided
L3 Cache Handle: Not Provided
Serial Number: Not Specified
Asset Tag: Not Specified
Part Number: Not Specified
Characteristics: None

在内核中使用dmi区分设备

有时候需要在内核中区分不同的设备,比如不同厂商设置的最大背光亮度值是不一样的,
有的厂商设置为100,有的设置为10,这时候可以用dmi来区分不同设备,针对性地进行设置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//callback 函数
static int video_set_bqc_offset(
const struct dmi_system_id *d)
{
if (hw_changes_brightness == -1)
hw_changes_brightness = 1;
return 0;
}

//特例表,如果当前设备匹配到了这个表,则调用 callback 函数进行特殊处理
//有好几个匹配项目,一般用 DMI_BOARD_VENDOR , DMI_BOARD_NAME 等
//其它的见内核源码 include/linux/mod_devicetable.h
static const struct dmi_system_id video_dmi_table[] = {
{
.callback = video_set_bqc_offset,
.ident = "Acer Aspire 5720",
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "Acer"),
DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5720"),
},
},
{}
};

//在合适的位置(比如 init 函数里)使用此函数去检查特例表。
dmi_check_system(video_dmi_table);

这里写了一个示例,实现了程序和内核交流的几种方式。

主要做了这些事情:在内核中增加一个新的系统调用,专门申请一块物理内存;实现一个内核模块,映射物理内存到用户层。实现用户层应用程序调用新增的系统调用,传入物理内存的大小和要写入的文件路径等参数,并将指定内存中的内容回写到本地磁盘。

内核模块

内核模块主要实现了ioctl , read , write , mmap 等接口

内核模块代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
chardevice.h
#ifndef CHARDEV_H
#define CHARDEV_H

#include <linux/ioctl.h>

#define MAX_SIZE 0x10000 /* max size (64kb) mmaped to userspace */
#define DEVICE_NAME "chardevice"
#define CLASS_NAME "ldj"
#define DEVICE_FILENAME "/dev/chardevice"
#define MAJOR_NUM 100

#define IOCTL_ALLOC_BUFFER _IOWR(MAJOR_NUM, 0, unsigned int)
#define IOCTL_SET_DATA _IOW(MAJOR_NUM, 1, char*)
#define IOCTL_GET_DATA _IOR(MAJOR_NUM, 2, char*)

#endif


chardevice.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <asm/uaccess.h>
#include <asm/errno.h>
#include "chardevice.h"

static struct class* class;
static struct device* device;
static char *sh_mem = NULL;

//打开文件
static int chardevice_open(struct inode *inodep, struct file *filep)
{
try_module_get(THIS_MODULE);
pr_info("chardevice: Device opened\n");

return 0;
}

//关闭文件
static int chardevice_release(struct inode *inodep, struct file *filep)
{
pr_info("chardevice: Device closed\n");

module_put(THIS_MODULE);
return 0;
}

//读取文件
static ssize_t chardevice_read(struct file *filep, char *buffer, size_t len, loff_t *offset)
{
int ret;

if (len > MAX_SIZE) {
pr_info("read overflow!\n");
ret = -EFAULT;
goto out;
}

if (copy_to_user(buffer, sh_mem, len) == 0) {
pr_info("chardevice: copy %lu char to the user\n", len);
ret = len;
} else {
ret = -EFAULT;
}

out:
return ret;
}

//写入文件
static ssize_t chardevice_write(struct file *filep, const char *buffer, size_t len, loff_t *offset)
{
int ret;

if (copy_from_user(sh_mem, buffer, len)) {
pr_err("chardevice: write fault!\n");
ret = -EFAULT;
goto out;
}
pr_info("chardevice: copy %lu char from the user\n", len);
ret = len;

out:
return ret;
}

//映射文件
static int chardevice_mmap(struct file *filp, struct vm_area_struct *vma)
{
int ret = 0;
struct page *page = NULL;
unsigned long size = (unsigned long)(vma->vm_end - vma->vm_start);

if(!sh_mem){
return -ENOMEM;
}

if (size > MAX_SIZE) {
ret = -EINVAL;
goto out;
}

page = virt_to_page((unsigned long)sh_mem + (vma->vm_pgoff << PAGE_SHIFT));
ret = remap_pfn_range(vma, vma->vm_start, page_to_pfn(page), size, vma->vm_page_prot);
if (ret != 0) {
goto out;
}

out:
return ret;
}

//控制文件
static long chardevice_ioctl(struct file *file, /* ditto */
unsigned int ioctl_num, /* number and param for ioctl */
unsigned long ioctl_param)
{
int i;
long ret = 0;
char __user *up;
char *kp;

switch (ioctl_num) {
//申请指定大小的内存空间
case IOCTL_ALLOC_BUFFER:
if(sh_mem){
kvfree(sh_mem);
}

if(ioctl_param > MAX_SIZE){
pr_err("chardevice: alloc buffer size is over than max : %u", MAX_SIZE );
}

sh_mem = kvmalloc(ioctl_param, GFP_KERNEL);
if (sh_mem == NULL) {
pr_info("chardevice: kvmalloc fail \n");
ret = -ENOMEM;
}

break;

//将用户态数据存储到刚申请的内存空间中
case IOCTL_SET_DATA:
up = (char __user *)ioctl_param;
kp = sh_mem;

get_user(kp[0], up);
for (i = 0; kp && i < MAX_SIZE; i++){
get_user(kp[i], up++);
}

break;

//刚申请的内存空间中数据导出到用户态空间
case IOCTL_GET_DATA:
up = (char __user *)ioctl_param;
kp = sh_mem;

for (i = 0; kp && i < MAX_SIZE; i++){
put_user(kp[i], up++);
}

put_user('\0', up);
break;
}

return ret;
}

static const struct file_operations chardevice_fops = {
.open = chardevice_open,
.read = chardevice_read,
.write = chardevice_write,
.release = chardevice_release,
.mmap = chardevice_mmap,
.unlocked_ioctl = chardevice_ioctl,
.owner = THIS_MODULE,
};

static int __init chardevice_init(void)
{
int ret = 0;
pr_info("chardevice: =========================== \n");
pr_info("chardevice: registing! \n");

ret = register_chrdev(MAJOR_NUM, DEVICE_NAME, &chardevice_fops);

if (ret < 0) {
pr_info("chardevice: fail to register_chrdev! \n");
goto out;
}

class = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(class)){
unregister_chrdev(MAJOR_NUM, DEVICE_NAME);
pr_info("mchar: failed to register device class");
ret = PTR_ERR(class);
goto out;
}

device = device_create(class, NULL, MKDEV(MAJOR_NUM, 0), NULL, DEVICE_NAME);
if (IS_ERR(device)) {
class_destroy(class);
unregister_chrdev(MAJOR_NUM, DEVICE_NAME);
ret = PTR_ERR(device);
goto out;
}


pr_info("chardevice: sprintf \n");
out:
return ret;
}

static void __exit chardevice_exit(void)
{
device_destroy(class, MKDEV(MAJOR_NUM, 0));
class_unregister(class);
class_destroy(class);
unregister_chrdev(MAJOR_NUM, DEVICE_NAME);
kvfree(sh_mem);

pr_info("chardevice: unregistered! \n");
}

module_init(chardevice_init);
module_exit(chardevice_exit);
MODULE_LICENSE("GPL");

Makefile

obj-m += chardevice.o

PWD := $(CURDIR)

all:
make modules M=$(PWD) -C /lib/modules/$(shell uname -r)/build

clean:
make clean M=$(PWD) -C /lib/modules/$(shell uname -r)/build

内核模块编译和运行

内核模块编译

1
make -j`nproc`

内核模块运行

1
sudo insmod ./chardevice.ko

用户态程序

用户态程序代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
test.c
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include "chardevice.h"

#define FILE_FILENAME "a.txt"

int ioctl_alloc_buffer(unsigned int len)
{
int fd_device, ret;

fd_device = open(DEVICE_FILENAME, O_RDWR|O_NDELAY);
if(fd_device < 0) {
printf("ioctl_alloc_buffer : fail to open %s \n", DEVICE_FILENAME);
return -1;
}

ret = ioctl(fd_device, IOCTL_ALLOC_BUFFER, len);

if (ret < 0) {
printf("ioctl_alloc_buffer : failed , ret = %d\n", ret);
}

printf("ioctl_alloc_buffer : success !\n");

close(fd_device);
return ret;
}

int ioctl_set_data(char *data)
{
int fd_device, ret;
char *p = NULL;
char buff[MAX_SIZE];

fd_device = open(DEVICE_FILENAME, O_RDWR|O_NDELAY);
if(fd_device < 0) {
printf("ioctl_set_data : fail to open %s \n", DEVICE_FILENAME);
return -1;
}

ret = ioctl(fd_device, IOCTL_SET_DATA, data);

if (ret < 0) {
printf("ioctl_set_data : failed , ret = %d\n", ret);
}

printf("ioctl_set_data : data = %s \n", data);

close(fd_device);
return ret;
}

int ioctl_get_data()
{
int fd_device, ret;
char *p = NULL;
char buff[MAX_SIZE];

fd_device = open(DEVICE_FILENAME, O_RDWR|O_NDELAY);
if(fd_device < 0) {
printf("ioctl_get_data : fail to open %s \n", DEVICE_FILENAME);
return -1;
}

ret = ioctl(fd_device, IOCTL_GET_DATA, buff);

if (ret < 0) {
printf("ioctl_get_data : failed , ret = %d\n", ret);
}

printf("ioctl_get_data : data = %s \n", buff);

close(fd_device);
return ret;
}

int file_mmap_write(char *data, unsigned int data_len)
{
int fd_device, ret;
char *p = NULL;
char buff[MAX_SIZE];

if(data_len > MAX_SIZE){
printf("file_mmap_write : error! mmap size over than %u \n", MAX_SIZE);
return -1;
}

fd_device = open(DEVICE_FILENAME, O_RDWR|O_NDELAY);
if(fd_device < 0) {
printf("file_mmap_write : fail to open %s \n", DEVICE_FILENAME);
return -1;
}

p = (char*)mmap(0, data_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd_device, 0);
printf("file_mmap_write : writing txt to driver , data = %s \n", data);
sprintf(p, "%s", data);

munmap(p, data_len);
close(fd_device);
}

int file_mmap_read(unsigned int data_len)
{
int fd_device, ret;
char *p = NULL;
char buff[MAX_SIZE];

if(data_len > MAX_SIZE){
printf("file_mmap_write : error! mmap size over than %u \n", MAX_SIZE);
return -1;
}

fd_device = open(DEVICE_FILENAME, O_RDWR|O_NDELAY);
if(fd_device < 0) {
printf("file_mmap_write : fail to open %s \n", DEVICE_FILENAME);
return -1;
}

p = (char*)mmap(0, data_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd_device, 0);
printf("file_mmap_read : reading txt from driver , data = %s \n", p);

munmap(p, data_len);
close(fd_device);
}

int file_write(char *data)
{
int fd_device, ret;

fd_device = open(DEVICE_FILENAME, O_RDWR|O_NDELAY);
if(fd_device < 0) {
printf("file_write : fail to open %s \n", DEVICE_FILENAME);
return -1;
}

ret = write(fd_device, data, MAX_SIZE);
if(fd_device < 0) {
printf("file_write : fail to write %s \n", DEVICE_FILENAME);
return -1;
}

printf("file_write : writing txt to driver , data = %s \n", data);

close(fd_device);
}

int file_read()
{
int fd_device, ret;
char buff[MAX_SIZE];

fd_device = open(DEVICE_FILENAME, O_RDWR|O_NDELAY);
if(fd_device < 0) {
printf("file_read : fail to open %s \n", DEVICE_FILENAME);
return -1;
}

ret = read(fd_device, buff, MAX_SIZE);
printf("file_read : reading txt from driver , data = %s \n", buff);

close(fd_device);
}

int test()
{
int fd_file, ret;
char buff[MAX_SIZE];

//从 a.txt 读取数据存到 buff 中
fd_file = open(FILE_FILENAME, O_RDWR|O_NDELAY);
if(fd_file < 0) {
printf("fail to open %s \n", FILE_FILENAME);
return -1;
}

ret = read(fd_file, buff, MAX_SIZE);
if(ret < 0){
printf("fail to read %s \n", FILE_FILENAME);
return ret;
}
else if(ret == 0)
{
printf("%s is empty \n", FILE_FILENAME);
}
else
{
printf("a.txt content = %s \n", buff);
}

//ioctl 测试

//请求驱动申请一块 MAX_SIZE 大小的内核内存
ioctl_alloc_buffer(MAX_SIZE);

//向内核内存存入 buff 中的数据
ioctl_set_data(buff);

//从内核内存获取数据并打印出来
ioctl_get_data();

//mmap 测试

//mmap 内核内存到用户态内存,并存入 buff 中的数据
file_mmap_write(buff, MAX_SIZE);

//mmap 内核内存到用户态内存,获取数据并打印出来
file_mmap_read(MAX_SIZE);

//read write 测试
//写 buff 到驱动驱动申请的内核内存
file_write(buff);

//读内核内存并打印出来
file_read();

}

extern char *optarg;

int main(int argc, char *argv[])
{
int fd_file, ret;
unsigned int buff_size;
char buff[MAX_SIZE], *file_path;
char optStr[] = "l:f:h";
int c;

//解析命令行参数
while ((c = getopt(argc, argv, optStr)) != -1) {
switch (c) {
case 'l':
printf("set buffer size = %s\n", optarg);
buff_size = atol(optarg);
break;
case 'f':
file_path = optarg;
printf("select file path = %s\n", file_path);
break;
case 'h':
printf("test -l <set_buffer_size> -f <file_to_copy>\n");
break;
}
}

//请求驱动申请一块 MAX_SIZE 大小的内核内存
if(buff_size <= 0){
printf("test : the size of buff is invalied , buff_size = %u \n", buff_size);
return -1;
}
ioctl_alloc_buffer(buff_size);

//从 a.txt 读取数据存到 buff 中
fd_file = open(file_path, O_RDWR|O_NDELAY);
if(fd_file < 0) {
printf("fail to open %s \n", file_path);
return -1;
}

ret = read(fd_file, buff, buff_size);
if(ret < 0){
printf("fail to read %s \n", file_path);
return ret;
}
else if(ret == 0)
{
printf("%s is empty \n", file_path);
}
else
{
printf("%s content = %s \n", file_path, buff);
}

file_mmap_write(buff, buff_size);

file_mmap_read(buff_size);

return 0;
}

用户态程序代码编译和运行

用户态程序代码编译

1
gcc test.c -o test

用户态程序代码运行

1
2
cat  "春眠不觉晓,处处闻啼鸟。夜来风雨声,花落知多少。" > a.txt
sudo ./test -l 65536 -f a.txt

用户态程序代码运行结果

1
2
3
4
5
6
set buffer size = 65536
select file path = a.txt
ioctl_alloc_buffer : success !
a.txt content = 春眠不觉晓,处处闻啼鸟。夜来风雨声,花落知多少。
file_mmap_write : writing txt to driver , data = 春眠不觉晓,处处闻啼鸟。夜来风雨声,花落知多少。
file_mmap_read : reading txt from driver , data = 春眠不觉晓,处处闻啼鸟。夜来风雨声,花落知多少。

什么是ACPI

ACPI是高级配置和电源接口的缩写。本意是要定义一套标准的、体系架构无关的接口用于实现对硬件的检测、配置和电源管理。由Intel,微软,东芝等诸多公司参与指定的行业规范,在X86和ARM系统中都有使用。

应用如何读取ACPI

1
sudo apt install acpica-tools
  • iasl: compiles ASL (ACPI Source Language) into AML (ACPI Machine Language), suitable for inclusion as a DSDT in system firmware.It also can disassemble AML, for debugging purposes.
  • acpibin: performs basic operations on binary AML files (e.g., comparison, data extraction)
  • acpidump: write out the current contents of ACPI tables
  • acpiexec: simulate AML execution in order to debug method definitions
  • acpihelp: display help messages describing ASL keywords and op-codes
  • acpisrc: manipulate the ACPICA source tree and format source files for specific environments
  • acpixtract: extract binary ACPI tables from acpidump output (see also the pmtools package)

列出所有ACPI的表

1
2
3
4
5
6
7
8
root@vm:~# acpidump -s
ACPI: SSDT 0x0000000000000000 0000CA (v01 BOCHS VMGENID 00000001 BXPC 00000001)
ACPI: APIC 0x0000000000000000 000090 (v01 BOCHS BXPC 00000001 BXPC 00000001)
ACPI: WAET 0x0000000000000000 000028 (v01 BOCHS BXPC 00000001 BXPC 00000001)
ACPI: DSDT 0x0000000000000000 003DD4 (v01 BOCHS BXPC 00000001 BXPC 00000001)
ACPI: FACP 0x0000000000000000 000074 (v01 BOCHS BXPC 00000001 BXPC 00000001)
ACPI: HPET 0x0000000000000000 000038 (v01 BOCHS BXPC 00000001 BXPC 00000001)
ACPI: FACS 0x0000000000000000 000040

生成某个表的二进制文件

1
2
3
root@vm:~# acpidump -n DSDT -b
root@vm:~# ls
dsdt.dat

把某个表的二进制文件转换成可读文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
root@vm:~# iasl -d dsdt.dat

Intel ACPI Component Architecture
ASL+ Optimizing Compiler/Disassembler version 20190509
Copyright (c) 2000 - 2019 Intel Corporation

File appears to be binary: found 4349 non-ASCII characters, disassembling
Binary file appears to be a valid ACPI table, disassembling
Input file dsdt.dat, Length 0x3DD4 (15828) bytes
ACPI: DSDT 0x0000000000000000 003DD4 (v01 BOCHS BXPC 00000001 BXPC 00000001)
Pass 1 parse of [DSDT]
Pass 2 parse of [DSDT]
Parsing Deferred Opcodes (Methods/Buffers/Packages/Regions)

Parsing completed
Disassembly completed
ASL Output: dsdt.dsl - 144810 bytes

读某个表的可读文件

节选自 dsdt.dsl 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
Scope (_SB)
{
Device (PCI0)
{
Name (_HID, EisaId ("PNP0A03") /* PCI Bus */) // _HID: Hardware ID
Name (_ADR, Zero) // _ADR: Address
Name (_UID, Zero) // _UID: Unique ID
Method (EDSM, 5, Serialized)
{
If ((Arg2 == Zero))
{
Local0 = Buffer (One)
{
0x00 // .
}
If ((Arg0 != ToUUID ("e5c937d0-3553-4d7a-9117-ea4d19c3434d") /* Device Labeling Interface */))
{
Return (Local0)
}

If ((Arg1 < 0x02))
{
Return (Local0)
}

Local0 [Zero] = 0x81
Return (Local0)
}

If ((Arg2 == 0x07))
{
Local0 = Package (0x02)
{
Zero,
""
}
Local1 = DerefOf (Arg4 [Zero])
Local0 [Zero] = Local1
Return (Local0)
}
}
}
}

内核如何读取ACPI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/*
* "num-pins" is the total number of interrupt pins implemented in
* this mbigen instance, and mbigen is an interrupt controller
* connected to ITS converting wired interrupts into MSI, so we
* use "num-pins" to alloc MSI vectors which are needed by client
* devices connected to it.
*
* Here is the DSDT device node used for mbigen in firmware:
* Device(MBI0) {
* Name(_HID, "HISI0152")
* Name(_UID, Zero)
* Name(_CRS, ResourceTemplate() {
* Memory32Fixed(ReadWrite, 0xa0080000, 0x10000)
* })
*
* Name(_DSD, Package () {
* ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"),
* Package () {
* Package () {"num-pins", 378}
* }
* })
* }
*/
ret = device_property_read_u32(&pdev->dev, "num-pins", &num_pins);
if (ret || num_pins == 0)
return -EINVAL;

在linux上使用 devmem2 查看设备寄存器

devmem2 源码

https://github.com/radii/devmem2/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
/*
* devmem2.c: Simple program to read/write from/to any location in memory.
*
* Copyright (C) 2000, Jan-Derk Bakker (jdb@lartmaker.nl)
*
*
* This software has been developed for the LART computing board
* (http://www.lart.tudelft.nl/). The development has been sponsored by
* the Mobile MultiMedia Communications (http://www.mmc.tudelft.nl/)
* and Ubiquitous Communications (http://www.ubicom.tudelft.nl/)
* projects.
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <ctype.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/mman.h>

#define FATAL do { fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", \
__LINE__, __FILE__, errno, strerror(errno)); exit(1); } while(0)

#define MAP_SIZE 4096UL
#define MAP_MASK (MAP_SIZE - 1)

int main(int argc, char **argv) {
int fd;
void *map_base, *virt_addr;
unsigned long read_result, writeval;
off_t target;
int access_type = 'w';

if(argc < 2) {
fprintf(stderr, "\nUsage:\t%s { address } [ type [ data ] ]\n"
"\taddress : memory address to act upon\n"
"\ttype : access operation type : [b]yte, [h]alfword, [w]ord\n"
"\tdata : data to be written\n\n",
argv[0]);
exit(1);
}
target = strtoul(argv[1], 0, 0);

if(argc > 2)
access_type = tolower(argv[2][0]);


if((fd = open("/dev/mem", O_RDWR | O_SYNC)) == -1) FATAL;
printf("/dev/mem opened.\n");
fflush(stdout);

/* Map one page */
map_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, target & ~MAP_MASK);
if(map_base == (void *) -1) FATAL;
printf("Memory mapped at address %p.\n", map_base);
fflush(stdout);

virt_addr = map_base + (target & MAP_MASK);
switch(access_type) {
case 'b':
read_result = *((unsigned char *) virt_addr);
break;
case 'h':
read_result = *((unsigned short *) virt_addr);
break;
case 'w':
read_result = *((unsigned long *) virt_addr);
break;
default:
fprintf(stderr, "Illegal data type '%c'.\n", access_type);
exit(2);
}
printf("Value at address 0x%X (%p): 0x%X\n", target, virt_addr, read_result);
fflush(stdout);

if(argc > 3) {
writeval = strtoul(argv[3], 0, 0);
switch(access_type) {
case 'b':
*((unsigned char *) virt_addr) = writeval;
read_result = *((unsigned char *) virt_addr);
break;
case 'h':
*((unsigned short *) virt_addr) = writeval;
read_result = *((unsigned short *) virt_addr);
break;
case 'w':
*((unsigned long *) virt_addr) = writeval;
read_result = *((unsigned long *) virt_addr);
break;
}
printf("Written 0x%X; readback 0x%X\n", writeval, read_result);
fflush(stdout);
}

if(munmap(map_base, MAP_SIZE) == -1) FATAL;
close(fd);
return 0;
}

devmem2 编译

1
2
gcc devmem2.c -o devmem2
sudo cp devmem2 /usr/bin/

devmem2 编译使用方法

一次只能读写一个地址

1
2
3
4
5
6
7
8
9
10
11
12
# 读
dh@dh:~$ sudo devmem2 0xb0400000
/dev/mem opened.
Memory mapped at address 0xb7f3b000.
Value at address 0xB0400000 (0xb7f3b000): 0xFFFFFFFF

#写
dh@dh:~$ sudo devmem2 0xb0400000 w 0x12345678
/dev/mem opened.
Memory mapped at address 0xb7ee5000.
Value at address 0xB0400000 (0xb7ee5000): 0xFFFFFFFF
Written 0x12345678; readback 0xFFFFFFFF

准备工具和源码

  1. 下载 vscode
1
https://code.visualstudio.com/
  1. 下载内核源码
1
2
3
4
mkdir -pv ~/ws
cd ~/ws
wget https://mirrors.ustc.edu.cn/kernel.org/linux/kernel/v5.x/linux-5.4.100.tar.xz
tar xf linux-5.4.100.tar.xz
  1. 系统必备包
1
2
3
# 基于 ubuntu 24.04
sudo apt install bear clangd git tmux
sudo apt install build-essential bc kmod cpio flex libncurses5-dev libelf-dev libssl-dev bison rsync lz4

配置工具

  1. 编译linux码
1
2
3
4
cd ~/ws/linux-5.4.100
make oldconfig
bear -- make -j12
# 编译完成后检查根目录是否生成了 compile_commands.json 文件
  1. 配置 vscode

在 vscode 中安装clangd插件,设置clangd的参数为,如下图

1
--compile-commands-dir=${workspaceFolder} --background-index --completion-style=detailed --header-insertion=never -log=info

clangd配置

搭建环境

hexo文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 基础系统 ubuntu 20.04
# 安装hexo
sudo apt install git nodejs npm
npm config set registry https://registry.npm.taobao.org
npm install -g hexo-cli

# hexo创建项目
hexo init liduanjun_github_page
cd liduanjun_github_page
npm install

# hexo配置主题
npm install hexo-theme-next
cd liduanjun_github_page
git clone https://github.com/next-theme/hexo-theme-next themes/next
nano _config.yml
# theme: next

开始写作

1
2
hexo new post "博文标题"
hexo server

Github Page 托管

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
mkdir -pv .github/workflows/
cat > .github/workflows/pages.yml <<EOF
name: Pages

on:
push:
branches:
- master # default branch

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
# If your repository depends on submodule, please see: https://github.com/actions/checkout
submodules: recursive
- name: Use Node.js 21.x
uses: actions/setup-node@v2
with:
node-version: '21'
- name: Cache NPM dependencies
uses: actions/cache@v2
with:
path: node_modules
key: ${{ runner.OS }}-npm-cache
restore-keys: |
${{ runner.OS }}-npm-cache
- name: Install Dependencies
run: npm install
- name: Build
run: npm run build
- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v2
with:
path: ./public
deploy:
needs: build
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v2
EOF

打开 liduanjun.github.io 项目的配置页面

1
https://github.com/liduanjun/liduanjun.github.io/settings/pages

点击 Code and automation > Pages

选择 Github Action 为 Github Action ,如下图

Github Page 配置

发布文章

1
2
git commit
git push origin master

注意!!!

如果有内核deb包,定制内核名,基本镜像中的内核包名,宿主机内核包名要一致,否则会定制失败

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
#!/bin/bash
#set -x

#基本镜像,指需要被定制的镜像
#定制镜像,指被定制加工完成的镜像
#[需要设置的部分]
#基本镜像的下载链接
ISO_SOURCE_URL=https://cdimage.uniontech.com/iso-v20/uniontechos-desktop-20-professional-1022_arm64.iso

#基本镜像存放文件夹的路径
ISO_SOURCE_PATH=cdimage

#基本镜像的文件名
ISO_SOURCE_NAME=uniontechos-desktop-20-professional-1022_arm64.iso

#定制镜像要保存的文件夹路径
ISO_DEST_PATH=cdimage

#定制镜像要保存的文件名
ISO_DEST_NAME=uniontechos-desktop-20-professional-1022_arm64-oem.iso

#定制的deb包路径
DEBS_PATH=debs

#定制的deb包(内核)路径,如果没有内核包请留空
DEBS_KERNEL_PATH=debs_kernel

#定制的deb包(内核) release 和 version,没有的话留空
# 例如,下行的 release 为 4.19.0-arm64-desktop,version 为 4.19.90-2001
# linux-image-4.19.0-arm64-desktop_4.19.90-2001_arm64.deb
# 因为宿主机的内核release必须和要定制的镜像的内核release一致,所以这里直接取宿主机内核release
DEBS_KERNEL_RELEASE=`uname -r`
DEBS_KERNEL_VERSION=4.19.90-2001

#[不需要设置的部分]
#临时文件夹:ISO解压后存放的路径
TEMP_ISO=iso

#临时文件夹:filesystem.squashfs解压后存放的路径
TEMP_ROOTFS=rootfs

USER=$(env | grep ^USER | cut -d "=" -f 2)
if [ $USER != "root" ];then
echo "请用root权限执行此脚本!"
exit
fi

if [ $# -eq 0 ];then
echo "用法:
1)$0 config 配置环境
2)将内核deb包放入 $DEBS_KERNEL_PATH 中去
3)将其它deb包放入 $DEBS_PATH 中去
4)$0 build 定制镜像"
exit
fi

#[只运行一次的命令]
if [ $# -eq 1 ] && [ $1 == "config" ];then
echo "====开始配置OEM环境"

#安装定制oem所需的依赖软件
echo "====安装定制oem所需的依赖软件"
apt install -y squashfs-tools dpkg-dev xorriso wget

#准备各个文件夹
mkdir -m 777 -pv $ISO_SOURCE_PATH $ISO_DEST_PATH $DEBS_PATH $DEBS_KERNEL_PATH
echo "文件夹创建成功"
echo "请把内核deb包放在$DEBS_KERNEL_PATH中"
echo "请把其它deb包放在$DEBS_PATH"

#下载基本镜像
echo "====下载基本镜像$ISO_SOURCE_NAME$ISO_SOURCE_PATH目录中去"
if [ ! -e $ISO_SOURCE_PATH/$ISO_SOURCE_NAME ];then
wget --no-check-certificate $ISO_SOURCE_URL -O $ISO_SOURCE_PATH/$ISO_SOURCE_NAME
fi

exit
fi

if [ $# -eq 1 ] && [ $1 == "build" ];then
echo "====开始制作OEM镜像"

# 内核deb包文件有四个:image和headers为必须安装,libc为推荐安装,dbg为可选安装
if [ -d $DEBS_KERNEL_PATH ];then
if [ -z $DEBS_KERNEL_RELEASE ] || [ -z $DEBS_KERNEL_VERSION ];then
echo "$DEBS_KERNEL_RELEASE 或者 $DEBS_KERNEL_VERSION 配置有误"
exit
fi

DEBS_LINUX_IMAGE=linux-image-${DEBS_KERNEL_RELEASE}_${DEBS_KERNEL_VERSION}_arm64.deb
DEBS_LINUX_HEADERS=linux-headers-${DEBS_KERNEL_RELEASE}_${DEBS_KERNEL_VERSION}_arm64.deb
DEBS_LINUX_LIBC=linux-libc-dev_${DEBS_KERNEL_VERSION}_arm64.deb
DEBS_LINUX_IMAGE_DBG=linux-image-${DEBS_KERNEL_RELEASE}-dbg_${DEBS_KERNEL_VERSION}_arm64.deb

if [ ! -e $DEBS_KERNEL_PATH/$DEBS_LINUX_IMAGE ] || [ ! -e $DEBS_KERNEL_PATH/$DEBS_LINUX_HEADERS ];then
echo "内核deb包文件有四个:image和headers为必须安装,libc为推荐安装,dbg为可选安装"
echo "如果你不想安装内核deb包,请删除 $DEBS_KERNEL_PATH 文件夹"
exit
fi
fi

#卸载dev和proc文件系统
echo "====卸载dev和proc文件系统"
umount $TEMP_ROOTFS/proc
umount $TEMP_ROOTFS/sys
umount $TEMP_ROOTFS/dev/pts
umount $TEMP_ROOTFS/dev
umount $TEMP_ROOTFS/mnt

#拷贝iso中所有文件到iso目录中
echo "====解压ISO文件到临时目录"
umount /mnt
mount $ISO_SOURCE_PATH/$ISO_SOURCE_NAME /mnt
rm -rf $TEMP_ISO
mkdir -pv $TEMP_ISO
cp -r /mnt/. $TEMP_ISO
rm -rf $TEMP_ROOTFS
mkdir -pv $TEMP_ROOTFS

#解压filesystem.squashfs到fs目录
echo "====解压filesystem.squashfs文件"
unsquashfs -f -d $TEMP_ROOTFS /mnt/live/filesystem.squashfs

#挂载本机/dev和/proc文件系统到rootfs目录下
echo "====挂载本机/dev和/proc文件系统到rootfs目录下"
mount --bind /proc/ $TEMP_ROOTFS/proc/
mount --bind /sys/ $TEMP_ROOTFS/sys/
mount --bind /dev/ $TEMP_ROOTFS/dev/
mount --bind /dev/pts $TEMP_ROOTFS/dev/pts

#chroot到$TEMP_ROOTFS系统并安装相关的deb(内核)包,安装完后退出rootfs系统
echo "====安装内核deb包"
if [ -n $DEBS_KERNEL_PATH ];then
umount $TEMP_ROOTFS/mnt/
mount --bind $DEBS_KERNEL_PATH/ $TEMP_ROOTFS/mnt/

#安装 linux-image
if [ -e $DEBS_KERNEL_PATH/$DEBS_LINUX_IMAGE ];then
chroot $TEMP_ROOTFS /bin/sh -c "sudo apt install /mnt/$DEBS_LINUX_IMAGE"
fi

#安装 linux-image-dbg
if [ -e $DEBS_KERNEL_PATH/$DEBS_LINUX_IMAGE_DBG ];then
chroot $TEMP_ROOTFS /bin/sh -c "sudo apt install /mnt/$DEBS_LINUX_IMAGE_DBG"
fi

#安装 linux-headers
if [ -e $DEBS_KERNEL_PATH/$DEBS_LINUX_HEADERS ];then
chroot $TEMP_ROOTFS /bin/sh -c "sudo apt install /mnt/$DEBS_LINUX_HEADERS"
fi

#安装 linux-libc-dev
if [ -e $DEBS_KERNEL_PATH/$DEBS_LINUX_LIBC ];then
chroot $TEMP_ROOTFS /bin/sh -c "sudo apt install /mnt/$DEBS_LINUX_LIBC"
fi
fi

#chroot到$TEMP_ROOTFS系统并安装相关的deb包,安装完后退出rootfs系统
echo "====安装其它deb包"
if [ -d $DEBS_PATH ];then
umount $TEMP_ROOTFS/mnt/
mount --bind $DEBS_PATH/ $TEMP_ROOTFS/mnt/
chroot $TEMP_ROOTFS /bin/sh -c "sudo apt install /mnt/*.deb"
fi

#制作安装器vmlinuz文件和initrd文件
echo "====制作安装器vmlinuz文件和initrd文件"
cp $TEMP_ROOTFS/boot/vmlinuz-$DEBS_KERNEL_RELEASE $TEMP_ISO/live/vmlinuz
cp $TEMP_ROOTFS/boot/initrd.img-$DEBS_KERNEL_RELEASE $TEMP_ISO/live/initrd.img

#卸载dev和proc文件系统
echo "====卸载dev和proc文件系统"
umount $TEMP_ROOTFS/proc
umount $TEMP_ROOTFS/sys
umount $TEMP_ROOTFS/dev/pts
umount $TEMP_ROOTFS/dev
umount $TEMP_ROOTFS/mnt

#将$TEMP_ROOTFS压缩为filesystem.squashfs
echo "====将$TEMP_ROOTFS压缩为filesystem.squashfs"
rm $TEMP_ISO/live/filesystem.squashfs
mksquashfs $TEMP_ROOTFS $TEMP_ISO/live/filesystem.squashfs -comp xz

#将iso目录压缩为iso镜像
echo "====将rootfs压缩为$TEMP_ISO"
xorriso -as mkisofs -r -J -c boot.cat -boot-load-size 4 -boot-info-table \
-eltorito-alt-boot -no-emul-boot -V "uos 20" -file_name_limit 250 -o \
$ISO_DEST_PATH/$ISO_DEST_NAME $TEMP_ISO

umount /mnt
rm -rf $TEMP_ROOTFS $TEMP_ISO

echo "====定制完成"
echo "定制镜像存放于:$ISO_SOURCE_PATH/$ISO_DEST_NAME"
exit
fi