LongLong's Blog

分享IT技术,分享生活感悟,热爱摄影,热爱航天。

Windows下的Linux子系统

适用于Linux的Windows子系统(Windows Subsystem for Linux, WSL)可让开发人员按原样运行GNU/Linux环境,包括大多数命令行工具、实用工具和应用程序。相比传统的虚拟机,WSL启动速度要快很多,相比Cygwin之类模拟器,WSL可以运行原生的Linux程序,同时WSL可以直接与Windows系统互通,可以在子系统环境中直接调用Windows程序,非常方便。

1. WSL的安装

最新版本的Windows 10已经可以完全支持WSL,微软给出的要求是Windows 10版本2004及更高版本(内部版本 19041 及更高版本)或Windows 11。安装方法为在“设置”中找到“启动或关闭Windows功能”,开启其中“适用于Linux的Windows子系统”和“虚拟机平台”两个功能,重启系统即可。

WSL有WSL1和WSL2之分,两个版本的运行方式有较大的差异:

WSL1更像是Cygwin,其和Windows环境不存在边界,即在Windows环境下可以直接看到子系统下的文件,带来的问题则是IO性能极差(应该是ext4到NFS文件系统的中间层消耗了大量的性能),解压一个40MB的文件居然需要20多分钟,并且CPU满负载运行。

WSL2则更像是虚拟机,其和Windows之间存在可见的边界,具有独立的网络和文件系统,文件系统也是一个虚拟硬盘文件,不过可以通过Windows的网络访问子系统内部的文件,相比使用sftp或是smb也还算是比较方便。今年好像还做到了在子系统内调用Windows下GPU的功能,但需要安装专门的GPU驱动。

2. 安装WSL系统

2.1 通过官方源安装

可以通过WSL命令行工具或是Microsoft store安装相应的Linux子系统,查询官方支持的Linux环境的命令如下:

wsl --list --online
以下是可安装的有效分发的列表。
请使用“wsl --install -d <分发>”安装。

NAME            FRIENDLY NAME
Ubuntu          Ubuntu
Debian          Debian GNU/Linux
kali-linux      Kali Linux Rolling
openSUSE-42     openSUSE Leap 42
SLES-12         SUSE Linux Enterprise Server v12
Ubuntu-16.04    Ubuntu 16.04 LTS
Ubuntu-18.04    Ubuntu 18.04 LTS
Ubuntu-20.04    Ubuntu 20.04 LTS

目前官方支持的Linux发行版主要以Ubuntu为主。使用wsl –install -d Ubuntu即可自动完成Linux子系统的安装。

2.2 手动安装

wsl命令行工具支持通过tar包的形式导入Linux子系统的根文件系统,以Gentoo为例,下载Gentoo的stage3包并导入的命令如下:

wget https://bouncer.gentoo.org/fetch/root/all/releases/amd64/autobuilds/20211101T222649Z/stage3-amd64-systemd-20211101T222649Z.tar.xz
wsl --import Gentoo Z:\WSL\Gentoo stage3-amd64-systemd-20211101T222649Z.tar.xz --version 2

导入命令的参数分别为导入后子系统的名称,安装路径,镜像文件的路径和使用的WSL版本,这里导入为WSL2。

3. 配置Arch Linux环境

在目前各种Linux发行版中定制化程度高,且比较易用的当属Arch Linux,下一版本的SteamOS也将基于Arch,这里介绍手动配置Arch Linux子系统的方法。

首先下载Arch Linux的最小化文件系统,当前的最新版本为archlinux-bootstrap-2021.11.01-x86_64.tar.gz,安装方式其实和Gentoo比较类似,但由于镜像文件路径的问题,先要对镜像文件进行简单的处理,由于镜像文件中存在同一个目录中具有文件名只是大小写不同的文件,为此需要在Linux环境进行处理,以root运行以下命令:

tar -xzpf archlinux-bootstrap-2021.11.01-x86_64.tar.gz
cd  root.x86_64
tar -czpf ../archlinux-bootstrap-2021.11.01-x86_64.tar.gz *

其实就是去掉镜像中root.x86_64这层目录。并升级WSL自带的内核,目前的内核是5.10,以管理员身份运行以下命令:

wsl --update

然后直接导入处理后镜像文件即可

wsl --import Arch Z:\WSL\Arch archlinux-bootstrap-2021.11.01-x86_64.tar.gz --version 2

完成后使用wsl命令可以进入安装好后的系统,并做基本的配置即可使用

wsl
#初始化pacman软件源
pacman-key --init
pacman-key --populate
pacman -Sy

在建立非root用户后,以非root用户登录是可以使用wsl命令行参数实现

#以非root用户登录,并进入home目录
wsl ~ -u longlong

4. 在Windows中访问子系统的文件

可以在文件浏览器中输入\wsl$\Arch,通过网络登录访问子系统中的文件,或者也可通过映射网络磁盘的方式进行挂载。子系统中则可以直接访问Windows的文件,全部的磁盘驱动器都会被挂在到/mnt下。

5. 在Linux子系统中使用GPU

在WSL中使用GPU需要在Windows主系统中安装对应的GPU驱动程序,御三家的WSL驱动如下,其中NVIDIA还有支持WSL的CUDA驱动

Intel GPU驱动

AMD GPU驱动

NVIDIA GPU驱动

使用Open Dynamics Engine实现刚体动力学仿真

刚体动力学一般力学的一个分支,研究刚体在外力作用下的运动规律。它是计算机器部件的运动,舰船、飞机、火箭等航行器的运动以及天体姿态运动的力学基础。

常见的多刚体动力学商业软件有RecurDyn,ADAMS等。而ODE (Open Dynamic Engine) 是一个免费的具有工业品质的刚体动力学的库,一款优秀的开源物理引擎,它为主程序员Russell Smith和几位开源社区贡献者共同努力下开发的。它能很好地仿真现实环境中的可移动物体,它是快速,强健和 可移植的。而且它有内建的碰撞检测系统。

1. ODE的安装

ODE的安装非常简单,从其官方网站https://www.ode.org,找到在BitBucket中的代码库地址,使用git直接下载整个代码库,并使用cmake进行编译安装。此外也可以使用各Linux发行版的包管理器进行安装。

pacman -S ode
#下载源代码
git clone https://bitbucket.org/odedevs/ode.git
cmake .
make

其中还附带了Python的API,在源代码的bindings/python目录下,运行其中的setup.py脚本可以完成Python接口的编译和安装。

python setup.py build
python setup.py install

2. ODE的模块组成

ODE中对于整个模型分为世界World,刚体Body,约束(铰接)Joints ,力Force,几何体Geometry等。通过在World中增加Boby及Joints等连接约束关系,建立整个多刚体等力学模型,并通过不断循环时间步长通过积分来计算整个系统的下个状态。代码的基本框架如下

import ode;
#创建时间
world = ode.World();
#设置重力
world.setGravity((0, -9.8, 0));
#新建一个刚体
b1 = ode.Body(world);
#设置时间补偿
TIME_STEP = 0.01;
#进行1000次积分循环
for i in range(0, 1000):
    #得到当前刚体的位置
    x1, y1, z1 = b1.getPosition();
    world.step(TIME_STEP);

3. 双摆的运动模拟及双摆曲线的绘制

建立一个双摆的模型并绘制两个摆动点的轨迹,两个质点的初始位置和初始速度分别为,(0, -0.5, 0),(10, 0, 0)和(0.5, -1.0, 0),(0, 0, 0)。并建立连接,由于这里是二维问题,使用球铰和圆柱铰并没有本质区别,就直接使用球铰了,其他类型的铰还需要给定方向等相关的参数。程序的代码如下

import numpy;
import ode;
from matplotlib import pyplot as plt;
import matplotlib.animation as animation;
if __name__ == '__main__':
    #创建世界
    world = ode.World();
    world.setGravity((0, -9.8, 0));
    #创建第一个质点
    b1 = ode.Body(world);
    #设置初始位置和速度
    b1.setPosition((0, -0.5, 0))
    b1.setLinearVel((10, 0, 0));
    #设置质量和外形
    m1 = ode.Mass();
    m1.setSphereTotal(0.1, 0.05);
    b1.setMass(m1);
    #创建第二个质点
    b2 = ode.Body(world);
    #设置初始位置,默认初始速度为0
    b2.setPosition((0.5, -1.0, 0));
    #设置质量和外形
    m2 = ode.Mass();
    m2.setSphereTotal(0.1, 0.05);
    b2.setMass(m2);
    #创建第一个质点和环境的约束
    j1 = ode.BallJoint(world);
    j1.attach(b1, ode.environment);
    j1.setAnchor((0.0, 0.0, 0.0));
    #创建第二个质点和第一个质点之间的约束
    j2 = ode.BallJoint(world);
    j2.attach(b1, b2);
    j2.setAnchor((0, -0.5, 0));
    #设置时间步长
    TIME_STEP = 0.01;
    fig, ax = plt.subplots();
    for i in range(0, 20000):
        #分别获得两个质点的位置
        x1, y1, z1 = b1.getPosition();
        x2, y2, z2 = b2.getPosition();
        #绘制当前的位置
        plt.plot(x1, y1, 'b,');
        plt.plot(x2, y2, 'r,');
        #进入下一时刻
        world.step(TIME_STEP);
    #设置绘图的X和Y轴保持相同的比例
    ax.set_aspect('equal');
    #保持绘制的图片
    plt.savefig('1.png');

由于Python接口只能支持Python2,如今已经几乎不能用了,附上C版本的代码

#include <ode/ode.h>
int main() {
    const dReal TIME_STEP = 0.01;
    const dReal TIME_STOP = TIME_STEP * 200;
    dInitODE();
    dWorldID w = dWorldCreate();
    dWorldSetGravity(w, 0.0, -9.8, 0.0);

    dBodyID b1 = dBodyCreate(w);
    dBodySetPosition(b1, 0.0, -0.5, 0.0);
    dBodySetLinearVel(b1, 10.0, 0.0, 0.0);
    dMass m1;
    dMassSetSphereTotal(&m1, 0.1, 0.05);
    dBodySetMass(b1, &m1);

    dBodyID b2 = dBodyCreate(w);
    dBodySetPosition(b2, 0.5, -1.0, 0.0);
    dMass m2;
    dMassSetSphereTotal(&m2, 0.1, 0.05);
    dBodySetMass(b2, &m2);

    dJointGroupID jd = dJointGroupCreate(10);

    dJointID j1 = dJointCreateBall(w, jd);
    dJointAttach(j1, b1, 0);
    dJointSetBallAnchor(j1, 0.0, 0.0, 0.0);

    dJointID j2 = dJointCreateBall(w, jd);
    dJointAttach(j2, b1, b2);
    dJointSetBallAnchor(j2, 0.0, -0.5, 0.0);

    dReal total_time = 0.0;
    while (total_time < TIME_STOP) {
        const dReal* u1 = dBodyGetPosition(b1);
        const dReal* u2 = dBodyGetPosition(b2);
        printf("%f\t%f\t%f\t%f\n", u1[0], u1[1], u2[0], u2[1]);
        dWorldStep(w, TIME_STEP);
        total_time += TIME_STEP;
    }
    dBodyDestroy(b1);
    dBodyDestroy(b2);
    dJointDestroy(j1);
    dJointDestroy(j2);
    dJointGroupDestroy(jd);
    dWorldDestroy(w);
    dCloseODE();
    return 0;
}

运行后得到的双摆轨迹曲线如下图所示,混乱中似乎有带有一些美感。