将 WSL2 从 C 盘迁移到其他硬盘

环境: Windows 11 + WSL2 + Ubuntu 22.04 目的: 将占用 C 盘 211 GB 的 WSL 虚拟磁盘迁移到其他硬盘,彻底释放 C 盘空间


背景

WSL2 默认将虚拟磁盘文件(ext4.vhdx)存储在 C 盘,路径为:

C:\Users\<用户名>\AppData\Local\Packages\CanonicalGroupLimited.Ubuntu22.04LTS_<hash>\LocalState\ext4.vhdx

随着在 WSL 内安装工具、存放项目文件,这个文件会不断膨胀。本文记录了将一个占用 211 GB 的 Ubuntu 22.04 实例完整迁移到 F 盘的全过程。


前置准备

  • 目标硬盘有足够空闲空间,建议至少是当前 vhdx 大小的 2 倍(需要临时存放 tar 备份)
  • PowerShell 以普通用户权限运行即可,无需管理员
  • 迁移期间 WSL 会关闭,正在运行的任务请提前保存

步骤总览

查看发行版名称 → 关闭 WSL → 导出 tar → 注销原版 → 导入新位置 → 修复用户 → 验证清理

详细步骤

Step 1:查看当前发行版名称

wsl --list --verbose

输出示例:

  NAME                   STATE           VERSION
* Ubuntu-22.04           Running         2
  podman-machine-default Stopped         2

记下要迁移的发行版名称,本文为 Ubuntu-22.04。带 * 号的是默认发行版。


Step 2:关闭 WSL

wsl --shutdown

⚠️ 必须先关闭 WSL,否则 vhdx 文件被占用,导出会失败。


Step 3:导出为 tar 文件(备份)

在目标盘创建存放目录,然后执行导出:

# 先建好目录
mkdir F:\WSL

# 导出
wsl --export Ubuntu-22.04 F:\WSL\ubuntu22.tar

导出过程中会显示文件大小和进度,完成后提示”操作成功完成”。

💡 tar 文件大小与 vhdx 相近,确保目标盘有足够空间再执行。


Step 4:注销原发行版

wsl --unregister Ubuntu-22.04

⚠️ 执行后 C 盘的 ext4.vhdx 会被立即删除,请确认 Step 3 的 tar 文件已成功生成再执行!


Step 5:导入到新位置

wsl --import Ubuntu-22.04 F:\WSL\Ubuntu22 F:\WSL\ubuntu22.tar --version 2

参数说明:

参数 说明
Ubuntu-22.04 发行版名称,可以和原来保持一致
F:\WSL\Ubuntu22 新 vhdx 的存放目录,会自动创建
F:\WSL\ubuntu22.tar Step 3 导出的备份文件
--version 2 使用 WSL2

Step 6:恢复默认登录用户

导入后默认以 root 登录,需要改回原来的普通用户。

方法一(推荐):

ubuntu2204 config --default-user 你的用户名

方法二: 在 WSL 内编辑配置文件:

sudo tee /etc/wsl.conf <<EOF
[user]
default=你的用户名
EOF

💡 用户名替换为实际的 Linux 用户名,可在迁移前用 whoami 命令确认。


Step 7:验证并清理

进入 WSL 验证迁移结果:

wsl ~

在 WSL 内确认用户名和文件完整性:

whoami      # 确认不是 root
ls ~        # 确认个人文件都在

确认一切正常后,删除临时 tar 文件释放空间:

del F:\WSL\ubuntu22.tar

✅ 迁移完成!C 盘释放了与原 vhdx 等量的空间,本例释放了约 211 GB


常见问题

Q:迁移后 VSCode Remote WSL 还能用吗?

可以。VSCode 通过发行版名称识别 WSL,只要名称不变,重新连接后一切正常。

Q:迁移后 vhdx 没有缩小怎么办?

WSL 内删除文件后 vhdx 不会自动缩小。可以用以下命令手动压缩(需要管理员 PowerShell):

wsl --shutdown

# 进入 diskpart
diskpart

在 diskpart 中依次执行:

select vdisk file="F:\WSL\Ubuntu22\ext4.vhdx"
attach vdisk readonly
compact vdisk
detach vdisk
exit

Q:可以同时运行多个不同位置的 WSL 实例吗?

可以。用 --import 时指定不同的名称和目录即可,WSL 支持同时管理多个发行版,位置互不影响。

MCU中如何将字符串指定到确定的链接地址

 

 

MCU中如何将字符串指定到确定的链接地址

要把某个字符串(常量)放到固定地址,推荐用“自定义段 + 链接脚本/散装文件固定该段地址”。不要只写一个固定地址指针(那样地址处的数据不会被链接器自动放进去,除非你另外烧录)。

下面分别给 GCC(ld)ARMCC(AC5 scatter) 的做法。


方案 A(推荐):放到固定 Flash 地址(由链接器保证)

1) C 里把字符串放到自定义 section

// 放在任意 .c 文件里
__attribute__((section(".fixed_str"), used))
const char g_fixed_str[] = "POWER_BOARD_APP:1.2.3";

used/KEEP 是为了防止链接优化把它丢掉。

2) GCC:在 .ld 里把该 section 固定到地址

在你的链接脚本里加一段(地址换成你要的,比如 Flash 尾部):

/* 例:固定到 0x0800FC00(请确认没和别的段冲突) */
.fixed_str 0x0800FC00 :
{
  KEEP(*(.fixed_str))
} > FLASH

方案 B:ARMCC(AC5) 用 scatter 文件固定地址

1) C 里同样放到指定 section 名

__attribute__((section("FIXED_STR"), used))
const char g_fixed_str[] = "POWER_BOARD_APP:1.2.3";

2) 在 .sct(scatter)里创建一个固定地址的执行区放它

示例结构(你需要把它合并到现有 scatter 中,地址/大小按需调整):

; 在 scatter 文件里新增一个执行区,把 FIXED_STR 放进去
LR_IROM1 0x08000000 0x00010000  {
  ER_IROM1 0x08000000 0x0000FC00  {  ; 先放常规 RO
    * (+RO)
  }
  ER_FIXED 0x0800FC00 0x00000400  {  ; 固定区域
    *(FIXED_STR)
  }
}

方案 C(仅“读取固定地址”):固定地址指针(不负责放置数据)

如果你只是想读取某固定地址已有的字符串(比如出厂信息区),可以:

#define FIXED_STR_ADDR (0x0800FC00u)
const char * const g_fixed_str_ptr = (const char *)FIXED_STR_ADDR;

注意:这不会把 “…” 放进该地址;该地址内容必须已被你单独烧录或由其他镜像生成。


关键注意点

  • 固定地址必须在有效的 Flash/RAM 范围内,且不与 .text/.rodata/.data/.bss 等重叠。

  • 常量字符串通常放 Flash(RO);放 RAM 需要另外做段和初始化策略。

 

U-boot无法使用NFS从Ubuntu中下载文件

最近在调试一块Linux板卡的时候需要在U-boot中挂载Linux镜像,但是发现总是超时挂载失败。

失败时的log输出

因为板卡是可以ping通Ubuntu主机的,所以可以基本可以排除是本地网络的问题。

既然考虑是NFS的问题,那么就需要先确定NFS的配置是否正确

先查看/etc/exports文件中的配置是否正确,确认无误后确定NFS根目录的权限是否正常,这些都确认没有问题后就可以进行下一步的检查了。

首先查看nfs的版本:

sudo cat /proc/fs/nfsd/versions
输出:
-2 +3 +4 +4.1 +4.2

可以看到当前Ubuntu主机没有支持V2,但是U-boot需要使用V2,所以这里需要打开NFS V2的支持。

这里有两种方式打开,如果是Ubuntu18.04的话,需要编辑配置文件的内容。

sudo vim /etc/default/nfs-kernel-server 
# Number of servers to start up
#RPCNFSDCOUNT=8
RPCNFSDCOUNT="-V 2 8"

# Runtime priority of server (see nice(1))
RPCNFSDPRIORITY=0

# Options for rpc.mountd.
# If you have a port-based firewall, you might want to set up
# a fixed port here using the --port option. For more information, 
# see rpc.mountd(8) or http://wiki.debian.org/SecuringNFS
# To disable NFSv4 on the server, specify '--no-nfs-version 4' here
#RPCMOUNTDOPTS="--manage-gids"
RPCMOUNTDOPTS="-V 2 --manage-gids"

# Do you want to start the svcgssd daemon? It is only required for Kerberos
# exports. Valid alternatives are "yes" and "no"; the default is "no".
NEED_SVCGSSD=""

# Options for rpc.svcgssd.
#RPCSVCGSSDOPTS=""
RPCSVCGSSDOPTS="--nfs-version 2,3,4 --debug --syslog"

修改后保存,退出,然后重启NFS服务就可以了。

sudo /etc/init.d/nfs-kernel-server restart

如果是Ubuntu20.04需要修改NFS的配置文件

sudo vim /etc/nfs.conf
#
# This is a general configuration for the
# NFS daemons and tools
#
[general]
pipefs-directory=/run/rpc_pipefs
#
[exports]
# rootdir=/export
#
[exportfs]
# debug=0
#
[gssd]
# verbosity=0
# rpc-verbosity=0
# use-memcache=0
# use-machine-creds=1
# use-gss-proxy=0
# avoid-dns=1
# limit-to-legacy-enctypes=0
# context-timeout=0
# rpc-timeout=5
# keytab-file=/etc/krb5.keytab
# cred-cache-directory=
# preferred-realm=
#
[lockd]
# port=0
# udp-port=0
#
[mountd]
# debug=0
manage-gids=y
# descriptors=0
# port=0
# threads=1
# reverse-lookup=n
# state-directory-path=/var/lib/nfs
# ha-callout=
#
[nfsdcld]
# debug=0
# storagedir=/var/lib/nfs/nfsdcld
#
[nfsdcltrack]
# debug=0
# storagedir=/var/lib/nfs/nfsdcltrack
#
[nfsd]
# debug=0
# threads=8
# host=
# port=0
# grace-time=90
# lease-time=90
udp=y
# tcp=y
vers2=y
# vers3=y
# vers4=y
# vers4.0=y
# vers4.1=y
# vers4.2=y
# rdma=n
# rdma-port=20049
#
[statd]
# debug=0
# port=0
# outgoing-port=0
# name=
# state-directory-path=/var/lib/nfs/statd
# ha-callout=
# no-notify=0
#
[sm-notify]
# debug=0
# force=0
# retry-time=900
# outgoing-port=
# outgoing-addr=
# lift-grace=y
#
[svcgssd]
# principal=

这个文件需要修改两个地方,一个是开启Version2的支持,另一个如果当前NFS不支持UDP也需要开启UDP的支持。是否已经支持UDP可以使用:

$netstat -a | grep "nfs"
#没有支持
tcp        0      0 0.0.0.0:nfs             0.0.0.0:*               LISTEN     
tcp6       0      0 [::]:nfs                [::]:*                  LISTEN  

#支持
tcp        0      0 0.0.0.0:nfs             0.0.0.0:*               LISTEN     
tcp6       0      0 [::]:nfs                [::]:*                  LISTEN     
udp        0      0 0.0.0.0:nfs             0.0.0.0:*                          
udp6       0      0 [::]:nfs                [::]:*    

如果至此依旧不能解决问题,请确认系统防火墙以及杀毒软件是否误杀,可以先都关闭后尝试。

Arduino与树莓派字节对齐问题记录

我在做一个小东西的时候用到了Arduino和树莓派,因为需要将数据采集端放的比较远所以没有直接将传感器接在树莓派上,而是选择了使用Arduino采集传感器数据,然后使用串口与树莓派通信。就在我很快写完两端的代码之后遇到了一个奇怪的问题,那就是明明数据接收解析过程没有问题,但是解析的数据怎么也不对。经过分析发现是两个平台数据长度和默认的对齐方式不同导致的,特此记录一下。

struct sensor_data{
    uint16_t hand;
    double ambient_temp;
    double object_temp;
    uint16_t tail;
}sensor_data;

我在两个平台定义了相同的结构体用来传输数据,在Arduino上将传感器数据获取到之后写入结构体,然后使用按字节从串口发送,因为两个平台都是小端模式,所以理论上在树莓派上我只需要按字节把接收的数据拷贝到相同结构体就可以获取到数据,但是,实际发现Arduino只发送了14字节,也就是说在Arduino平台下double只占用了4字节,与float是一致的。而且是按2字节对齐。

A5 5A D0 A3 E2 41 30 5C E1 41 0F F0

实际打印的数据也印证了我的猜想,后来发现Arduino的文档中关于double的描述明确指出了这个问题。

文档中关于double的说明

树莓派自然是不存在double与float长度一致的问题,而且树莓派是4字节对齐,所以就会出现我遇到的数据错误的问题。知道问题所在,修改代码就愉快的解决问题了。

关于C语言中的字节对齐一般有两种方式:

一、强制按字节对齐:

#pragma pack (n)    //C编译器将按照n个字节对齐。
#pragma pack ()     //取消自定义字节对齐方式。

二、对齐到n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐:

__attribute((aligned (n)))  //C编译器将按照n个字节对齐。
__attribute ((packed))      //取消自定义字节对齐方式。

最后附上此次测试的代码:

树莓派:

#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <wiringPi.h>
#include <wiringSerial.h>

#pragma pack (2)
struct sensor_data{
    uint16_t hand;
    uint16_t serial_number;
    float ambient_temp;
    float object_temp;
    uint16_t tail;
}sensor_data;
#pragma pack ()

struct sensor_data rx_data;

int main(void)
{
    int hs1;
    char uart_rx_buffer[64];
    uint16_t uart_flag = 0;
    int16_t index = 0;
    int16_t data_flag = 0;
    wiringPiSetup();                            // 使用wiring编码去初始化GPIO序号
    // hs1 = serialOpen("/dev/ttyS0", 115200);     // 打开 /dev/ttyS0 串口设备,波特率115200
    hs1 = serialOpen("/dev/ttyUSB0", 115200); // 打开 /dev/ttyUSB0 串口设备,波特率115200
    if(hs1 < 0){
        printf("UART open error %d!\r\n",hs1);
        return -1;
    }
    else{
        printf("UART open success %d!\r\n",hs1);
    }

    char keyboard_input[10] = {};
    while(TRUE){

        int buffer_size = serialDataAvail(hs1);
        while(buffer_size){
            char c = serialGetchar(hs1);    // 从接收缓存区读取一个字节
            if((uart_flag == 0)&&(c == 0xA5)){
                uart_flag = 1;
            }
            else if((uart_flag == 1)&&(c == 0x5A)){
                uart_flag = 2;
            }
            else if((uart_flag == 1)&&(c != 0x5A)){
                uart_flag = 0;
                index = 0;
                memset(uart_rx_buffer,0,sizeof(uart_rx_buffer));
            }

            if(uart_flag > 0){
                uart_rx_buffer[index++] = c;
                if(index > sizeof(rx_data)+1){
                    uart_flag = 0;
                    index = 0;
                    memset(uart_rx_buffer,0,sizeof(uart_rx_buffer));
                }
            }

            if((uart_flag == 2)&&(c == 0x0F)){
                uart_flag = 3;
            }
            else if((uart_flag == 3)&&(c == 0xF0)){
                data_flag = 1;
                memcpy((&rx_data),uart_rx_buffer,sizeof(rx_data));
                uart_flag = 0;
                index = 0;
                memset(uart_rx_buffer,0,sizeof(uart_rx_buffer));
            }
            else if((uart_flag == 3)&&(c != 0xF0)){
                uart_flag = 0;
                index = 0;
                memset(uart_rx_buffer,0,sizeof(uart_rx_buffer));
            }
            buffer_size --;
        }

        if(data_flag){
            printf("%5d Ambient = %5.2f   Object = %5.2f\r\n",\
                  rx_data.serial_number,\
                  rx_data.ambient_temp,\
                  rx_data.object_temp);
            data_flag = 0;
        }
    }

    serialClose(hs1);                           // 关闭串口
    return 0;
}

因为使用到了wiringPi的库,所以编译的时候记得加-lwiringPi链接,执行的时候也需要使用管理员权限才可执行。

Arduino:

#include <Wire.h>
#include <Adafruit_MLX90614.h>

Adafruit_MLX90614 mlx = Adafruit_MLX90614();

struct sensor_data{
  uint16_t hand;
  uint16_t serial_number;
  float ambient_temp;
  float object_temp;
  uint16_t tail;
}sensor_data;

struct sensor_data tx_data;
uint16_t num =0;

void setup() {
  tx_data.hand = 0x5aa5;
  tx_data.tail = 0xf00f;
  Serial.begin(115200);
  mlx.begin();  
}

void loop() {
  tx_data.serial_number = num;
  tx_data.ambient_temp = mlx.readAmbientTempC();
  tx_data.object_temp = mlx.readObjectTempC();
  Serial.write((char*)(&tx_data),sizeof(tx_data));
  num++;
  delay(500);
}