Bladeren bron

feat(driver/net): 实现Loopback网卡接口 (#845)

* 初步实现loopback设备
SMALLC 8 maanden geleden
bovenliggende
commit
1ea2daad81

+ 484 - 0
kernel/src/driver/net/loopback.rs

@@ -0,0 +1,484 @@
+use crate::arch::rand::rand;
+use crate::driver::base::class::Class;
+use crate::driver::base::device::bus::Bus;
+use crate::driver::base::device::driver::Driver;
+use crate::driver::base::device::{Device, DeviceType, IdTable};
+use crate::driver::base::kobject::{KObjType, KObject, KObjectState};
+use crate::init::initcall::INITCALL_DEVICE;
+use crate::libs::spinlock::SpinLock;
+use crate::net::{generate_iface_id, NET_DEVICES};
+use crate::time::Instant;
+use alloc::collections::VecDeque;
+use alloc::fmt::Debug;
+use alloc::string::{String, ToString};
+use alloc::sync::{Arc, Weak};
+use alloc::vec::Vec;
+use core::cell::UnsafeCell;
+use core::ops::{Deref, DerefMut};
+use log::debug;
+use smoltcp::wire::HardwareAddress;
+use smoltcp::{
+    phy::{self},
+    wire::{IpAddress, IpCidr},
+};
+use system_error::SystemError;
+use unified_init::macros::unified_init;
+
+use super::NetDevice;
+
+const DEVICE_NAME: &str = "loopback";
+
+/// ## 环回接收令牌
+/// 用于储存lo网卡接收到的数据
+pub struct LoopbackRxToken {
+    buffer: Vec<u8>,
+}
+
+impl phy::RxToken for LoopbackRxToken {
+    /// ## 实现Rxtoken的consume函数
+    /// 接受一个函数 `f`,并在 `self.buffer` 上调用它。
+    ///
+    /// ## 参数
+    /// - mut self :一个可变的 `LoopbackRxToken` 实例。
+    /// - f :接受一个可变的 u8 切片,并返回类型 `R` 的结果。
+    ///
+    /// ## 返回值
+    /// 返回函数 `f` 在 `self.buffer` 上的调用结果。
+    fn consume<R, F>(mut self, f: F) -> R
+    where
+        F: FnOnce(&mut [u8]) -> R,
+    {
+        f(self.buffer.as_mut_slice())
+    }
+}
+
+/// ## 环回发送令牌
+/// 返回驱动用于操作lo设备
+pub struct LoopbackTxToken {
+    driver: LoopbackDriver,
+}
+
+impl phy::TxToken for LoopbackTxToken {
+    /// ## 实现TxToken的consume函数
+    /// 向lo的队列推入待发送的数据报,实现环回
+    ///
+    /// ## 参数
+    /// - self
+    /// - len:数据包的长度
+    /// - f:接受一个可变的 u8 切片,并返回类型 `R` 的结果。
+    ///
+    /// ## 返回值
+    /// 返回f对数据包操纵的结果
+    fn consume<R, F>(self, len: usize, f: F) -> R
+    where
+        F: FnOnce(&mut [u8]) -> R,
+    {
+        let mut buffer = vec![0; len];
+        let result = f(buffer.as_mut_slice());
+        let mut device = self.driver.inner.lock();
+        device.loopback_transmit(buffer);
+        result
+    }
+}
+
+/// ## Loopback设备
+/// 成员是一个队列,用来存放接受到的数据包。
+/// 当使用lo发送数据包时,不会把数据包传到link层,而是直接发送到该队列,实现环回。
+pub struct Loopback {
+    //回环设备的缓冲区,接受的数据包会存放在这里,发送的数据包也会发送到这里,实现环回
+    queue: VecDeque<Vec<u8>>,
+}
+
+impl Loopback {
+    /// ## Loopback创建函数
+    /// 创建lo设备
+    pub fn new() -> Self {
+        let queue = VecDeque::new();
+        Loopback { queue }
+    }
+    /// ## Loopback处理接受到的数据包函数
+    /// Loopback接受到数据后会调用这个函数来弹出接收的数据,返回给协议栈
+    ///
+    /// ## 参数
+    /// - &mut self :自身可变引用
+    ///
+    /// ## 返回值
+    /// - queue的头部数据包
+    pub fn loopback_receive(&mut self) -> Vec<u8> {
+        let buffer = self.queue.pop_front();
+        match buffer {
+            Some(buffer) => {
+                //debug!("lo receive:{:?}", buffer);
+                return buffer;
+            }
+            None => {
+                return Vec::new();
+            }
+        }
+    }
+    /// ## Loopback发送数据包的函数
+    /// Loopback发送数据包给自己的接收队列,实现环回
+    ///
+    /// ## 参数
+    /// - &mut self:自身可变引用
+    /// - buffer:需要发送的数据包
+    pub fn loopback_transmit(&mut self, buffer: Vec<u8>) {
+        //debug!("lo transmit!");
+        self.queue.push_back(buffer)
+    }
+}
+
+/// ## driver的包裹器
+/// 为实现获得不可变引用的Interface的内部可变性,故为Driver提供UnsafeCell包裹器
+///
+/// 参考virtio_net.rs
+struct LoopbackDriverWapper(UnsafeCell<LoopbackDriver>);
+unsafe impl Send for LoopbackDriverWapper {}
+unsafe impl Sync for LoopbackDriverWapper {}
+
+/// ## deref 方法返回一个指向 `LoopbackDriver` 的引用。
+impl Deref for LoopbackDriverWapper {
+    type Target = LoopbackDriver;
+    fn deref(&self) -> &Self::Target {
+        unsafe { &*self.0.get() }
+    }
+}
+/// ## `deref_mut` 方法返回一个指向可变 `LoopbackDriver` 的引用。
+impl DerefMut for LoopbackDriverWapper {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        unsafe { &mut *self.0.get() }
+    }
+}
+
+impl LoopbackDriverWapper {
+    /// ## force_get_mut返回一个指向可变 `LoopbackDriver` 的引用。
+    #[allow(clippy::mut_from_ref)]
+    #[allow(clippy::mut_from_ref)]
+    fn force_get_mut(&self) -> &mut LoopbackDriver {
+        unsafe { &mut *self.0.get() }
+    }
+}
+
+/// ## Loopback驱动
+/// 负责操作Loopback设备实现基本的网卡功能
+pub struct LoopbackDriver {
+    pub inner: Arc<SpinLock<Loopback>>,
+}
+
+impl LoopbackDriver {
+    /// ## LoopbackDriver创建函数
+    pub fn new() -> Self {
+        let inner = Arc::new(SpinLock::new(Loopback::new()));
+        LoopbackDriver { inner }
+    }
+}
+
+impl Clone for LoopbackDriver {
+    fn clone(&self) -> Self {
+        LoopbackDriver {
+            inner: self.inner.clone(),
+        }
+    }
+}
+
+impl phy::Device for LoopbackDriver {
+    type RxToken<'a> = LoopbackRxToken where Self: 'a;
+    type TxToken<'a> = LoopbackTxToken where Self: 'a;
+    /// ## 返回设备的物理层特性。
+    /// lo设备的最大传输单元为65535,最大突发大小为1,传输介质默认为Ethernet
+    fn capabilities(&self) -> phy::DeviceCapabilities {
+        let mut result = phy::DeviceCapabilities::default();
+        result.max_transmission_unit = 65535;
+        result.max_burst_size = Some(1);
+        result.medium = smoltcp::phy::Medium::Ethernet;
+        return result;
+    }
+    /// ## Loopback驱动处理接受数据事件
+    /// 驱动调用Loopback的receive函数,处理buffer封装成(rx,tx)返回给上层
+    ///
+    /// ## 参数
+    /// - `&mut self` :自身可变引用
+    /// - `_timestamp`
+    ///
+    /// ## 返回值
+    /// - None: 如果接收队列为空,返回 `None`,以通知上层没有可以接收的包
+    /// - Option::Some((rx, tx)):如果接收队列不为空,返回 `Some`,其中包含一个接收令牌 `rx` 和一个发送令牌 `tx`
+    fn receive(
+        &mut self,
+        _timestamp: smoltcp::time::Instant,
+    ) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
+        let buffer = self.inner.lock().loopback_receive();
+        //receive队列为为空,返回NONE值以通知上层没有可以receive的包
+        if buffer.is_empty() {
+            return Option::None;
+        }
+        let rx = LoopbackRxToken { buffer };
+        let tx = LoopbackTxToken {
+            driver: self.clone(),
+        };
+        return Option::Some((rx, tx));
+    }
+    /// ## Loopback驱动处理发送数据包事件
+    /// Loopback驱动在需要发送数据时会调用这个函数来获取一个发送令牌。
+    ///
+    /// ## 参数
+    /// - `&mut self` :自身可变引用
+    /// - `_timestamp`
+    ///
+    /// ## 返回值
+    /// - 返回一个 `Some`,其中包含一个发送令牌,该令牌包含一个对自身的克隆引用
+    fn transmit(&mut self, _timestamp: smoltcp::time::Instant) -> Option<Self::TxToken<'_>> {
+        Some(LoopbackTxToken {
+            driver: self.clone(),
+        })
+    }
+}
+
+/// ## LoopbackInterface结构
+/// 封装驱动包裹器和iface,设置接口名称
+pub struct LoopbackInterface {
+    driver: LoopbackDriverWapper,
+    iface_id: usize,
+    iface: SpinLock<smoltcp::iface::Interface>,
+    name: String,
+}
+
+impl LoopbackInterface {
+    /// ## `new` 是一个公共函数,用于创建一个新的 `LoopbackInterface` 实例。
+    /// 生成一个新的接口 ID。创建一个新的接口配置,设置其硬件地址和随机种子,使用接口配置和驱动器创建一个新的 `smoltcp::iface::Interface` 实例。
+    /// 设置接口的 IP 地址为 127.0.0.1。
+    /// 创建一个新的 `LoopbackDriverWapper` 实例,包装驱动器。
+    /// 创建一个新的 `LoopbackInterface` 实例,包含驱动器、接口 ID、接口和名称,并将其封装在一个 `Arc` 中。
+    /// ## 参数
+    /// - `driver`:一个 `LoopbackDriver` 实例,用于驱动网络环回操作。
+    ///
+    /// ## 返回值
+    /// 返回一个 `Arc<Self>`,即一个指向新创建的 `LoopbackInterface` 实例的智能指针。
+    pub fn new(mut driver: LoopbackDriver) -> Arc<Self> {
+        let iface_id = generate_iface_id();
+        let mut iface_config = smoltcp::iface::Config::new(HardwareAddress::Ethernet(
+            smoltcp::wire::EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]),
+        ));
+        iface_config.random_seed = rand() as u64;
+
+        let mut iface =
+            smoltcp::iface::Interface::new(iface_config, &mut driver, Instant::now().into());
+        //设置网卡地址为127.0.0.1
+        iface.update_ip_addrs(|ip_addrs| {
+            ip_addrs
+                .push(IpCidr::new(IpAddress::v4(127, 0, 0, 1), 8))
+                .unwrap();
+        });
+        let driver = LoopbackDriverWapper(UnsafeCell::new(driver));
+        Arc::new(LoopbackInterface {
+            driver,
+            iface_id,
+            iface: SpinLock::new(iface),
+            name: "lo".to_string(),
+        })
+    }
+}
+
+impl Debug for LoopbackInterface {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        f.debug_struct("LoopbackInterface")
+            .field("iface_id", &self.iface_id)
+            .field("iface", &"smtoltcp::iface::Interface")
+            .field("name", &self.name)
+            .finish()
+    }
+}
+//TODO: 向sysfs注册lo设备
+impl KObject for LoopbackInterface {
+    fn as_any_ref(&self) -> &dyn core::any::Any {
+        self
+    }
+
+    fn set_inode(&self, _inode: Option<Arc<crate::filesystem::kernfs::KernFSInode>>) {
+        todo!()
+    }
+
+    fn inode(&self) -> Option<Arc<crate::filesystem::kernfs::KernFSInode>> {
+        todo!()
+    }
+
+    fn parent(&self) -> Option<alloc::sync::Weak<dyn KObject>> {
+        todo!()
+    }
+
+    fn set_parent(&self, _parent: Option<alloc::sync::Weak<dyn KObject>>) {
+        todo!()
+    }
+
+    fn kset(&self) -> Option<Arc<crate::driver::base::kset::KSet>> {
+        todo!()
+    }
+
+    fn set_kset(&self, _kset: Option<Arc<crate::driver::base::kset::KSet>>) {
+        todo!()
+    }
+
+    fn kobj_type(&self) -> Option<&'static dyn crate::driver::base::kobject::KObjType> {
+        todo!()
+    }
+
+    fn name(&self) -> String {
+        self.name.clone()
+    }
+
+    fn set_name(&self, _name: String) {
+        todo!()
+    }
+
+    fn kobj_state(
+        &self,
+    ) -> crate::libs::rwlock::RwLockReadGuard<crate::driver::base::kobject::KObjectState> {
+        todo!()
+    }
+
+    fn kobj_state_mut(
+        &self,
+    ) -> crate::libs::rwlock::RwLockWriteGuard<crate::driver::base::kobject::KObjectState> {
+        todo!()
+    }
+
+    fn set_kobj_state(&self, _state: KObjectState) {
+        todo!()
+    }
+
+    fn set_kobj_type(&self, _ktype: Option<&'static dyn KObjType>) {
+        todo!()
+    }
+}
+
+impl Device for LoopbackInterface {
+    fn dev_type(&self) -> DeviceType {
+        DeviceType::Net
+    }
+
+    fn id_table(&self) -> IdTable {
+        IdTable::new(DEVICE_NAME.to_string(), None)
+    }
+
+    fn set_bus(&self, _bus: Option<Weak<dyn Bus>>) {
+        todo!()
+    }
+
+    fn set_class(&self, _class: Option<Weak<dyn Class>>) {
+        todo!()
+    }
+
+    fn driver(&self) -> Option<Arc<dyn Driver>> {
+        todo!()
+    }
+
+    fn set_driver(&self, _driver: Option<Weak<dyn Driver>>) {
+        todo!()
+    }
+
+    fn is_dead(&self) -> bool {
+        todo!()
+    }
+
+    fn can_match(&self) -> bool {
+        todo!()
+    }
+
+    fn set_can_match(&self, _can_match: bool) {
+        todo!()
+    }
+
+    fn state_synced(&self) -> bool {
+        true
+    }
+}
+
+impl NetDevice for LoopbackInterface {
+    /// 由于lo网卡设备不是实际的物理设备,其mac地址需要手动设置为一个默认值,这里默认为0200000001
+    fn mac(&self) -> smoltcp::wire::EthernetAddress {
+        let mac = [0x02, 0x00, 0x00, 0x00, 0x00, 0x01];
+        smoltcp::wire::EthernetAddress(mac)
+    }
+
+    #[inline]
+    fn nic_id(&self) -> usize {
+        self.iface_id
+    }
+
+    #[inline]
+    fn name(&self) -> String {
+        self.name.clone()
+    }
+    /// ## `update_ip_addrs` 用于更新接口的 IP 地址。
+    ///
+    /// ## 参数
+    /// - `&self` :自身引用
+    /// - `ip_addrs` :一个包含 `smoltcp::wire::IpCidr` 的切片,表示要设置的 IP 地址和子网掩码
+    ///
+    /// ## 返回值
+    /// - 如果 `ip_addrs` 的长度不为 1,返回 `Err(SystemError::EINVAL)`,表示输入参数无效
+    /// - 如果更新成功,返回 `Ok(())`
+    fn update_ip_addrs(
+        &self,
+        ip_addrs: &[smoltcp::wire::IpCidr],
+    ) -> Result<(), system_error::SystemError> {
+        if ip_addrs.len() != 1 {
+            return Err(SystemError::EINVAL);
+        }
+
+        self.iface.lock().update_ip_addrs(|addrs| {
+            let dest = addrs.iter_mut().next();
+
+            if let Some(dest) = dest {
+                *dest = ip_addrs[0];
+            } else {
+                addrs.push(ip_addrs[0]).expect("Push ipCidr failed: full");
+            }
+        });
+        return Ok(());
+    }
+    /// ## `poll` 用于轮询接口的状态。
+    ///
+    /// ## 参数
+    /// - `&self` :自身引用
+    /// - `sockets` :一个可变引用到 `smoltcp::iface::SocketSet`,表示要轮询的套接字集
+    ///
+    /// ## 返回值
+    /// - 如果轮询成功,返回 `Ok(())`
+    /// - 如果轮询失败,返回 `Err(SystemError::EAGAIN_OR_EWOULDBLOCK)`,表示需要再次尝试或者操作会阻塞
+    fn poll(&self, sockets: &mut smoltcp::iface::SocketSet) -> Result<(), SystemError> {
+        let timestamp: smoltcp::time::Instant = Instant::now().into();
+        let mut guard = self.iface.lock();
+        let poll_res = guard.poll(timestamp, self.driver.force_get_mut(), sockets);
+        if poll_res {
+            return Ok(());
+        }
+        return Err(SystemError::EAGAIN_OR_EWOULDBLOCK);
+    }
+
+    #[inline(always)]
+    fn inner_iface(&self) -> &SpinLock<smoltcp::iface::Interface> {
+        return &self.iface;
+    }
+}
+
+pub fn loopback_probe() {
+    loopback_driver_init();
+}
+/// ## lo网卡设备初始化函数
+/// 创建驱动和iface,初始化一个lo网卡,添加到全局NET_DEVICES中
+pub fn loopback_driver_init() {
+    let driver = LoopbackDriver::new();
+    let iface = LoopbackInterface::new(driver);
+
+    NET_DEVICES
+        .write_irqsave()
+        .insert(iface.iface_id, iface.clone());
+}
+
+/// ## lo网卡设备的注册函数
+#[unified_init(INITCALL_DEVICE)]
+pub fn loopback_init() -> Result<(), SystemError> {
+    loopback_probe();
+    return Ok(());
+}

+ 1 - 0
kernel/src/driver/net/mod.rs

@@ -11,6 +11,7 @@ use system_error::SystemError;
 mod dma;
 pub mod e1000e;
 pub mod irq_handle;
+pub mod loopback;
 pub mod virtio_net;
 
 pub trait NetDevice: Device {

+ 0 - 4
kernel/src/init/initial_kthread.rs

@@ -35,10 +35,8 @@ fn kernel_init() -> Result<(), SystemError> {
 
     #[cfg(target_arch = "x86_64")]
     crate::driver::disk::ahci::ahci_init().expect("Failed to initialize AHCI");
-
     virtio_probe();
     mount_root_fs().expect("Failed to mount root fs");
-
     e1000e_init();
     net_init().unwrap_or_else(|err| {
         error!("Failed to initialize network: {:?}", err);
@@ -97,7 +95,6 @@ fn try_to_run_init_process(path: &str, trap_frame: &mut TrapFrame) -> Result<(),
         }
         return Err(e);
     }
-
     Ok(())
 }
 
@@ -107,6 +104,5 @@ fn run_init_process(path: String, trap_frame: &mut TrapFrame) -> Result<(), Syst
 
     compiler_fence(Ordering::SeqCst);
     Syscall::do_execve(path, argv, envp, trap_frame)?;
-
     Ok(())
 }

+ 3 - 1
kernel/src/net/net_core.rs

@@ -43,7 +43,9 @@ pub fn net_init() -> Result<(), SystemError> {
 fn dhcp_query() -> Result<(), SystemError> {
     let binding = NET_DEVICES.write_irqsave();
 
-    let net_face = binding.get(&0).ok_or(SystemError::ENODEV)?.clone();
+    //由于现在os未实现在用户态为网卡动态分配内存,而lo网卡的id最先分配且ip固定不能被分配
+    //所以特判取用id为1的网卡(也就是virto_net)
+    let net_face = binding.get(&1).ok_or(SystemError::ENODEV)?.clone();
 
     drop(binding);
 

+ 3 - 0
user/apps/test_lo/.gitignore

@@ -0,0 +1,3 @@
+/target
+Cargo.lock
+/install/

+ 7 - 0
user/apps/test_lo/Cargo.toml

@@ -0,0 +1,7 @@
+[package]
+name = "test_lo"
+version = "0.1.0"
+edition = "2021"
+description = "测试lo网卡功能"
+authors = [ "smallc <2628035541@qq.com>" ]
+

+ 56 - 0
user/apps/test_lo/Makefile

@@ -0,0 +1,56 @@
+TOOLCHAIN=
+RUSTFLAGS=
+
+ifdef DADK_CURRENT_BUILD_DIR
+# 如果是在dadk中编译,那么安装到dadk的安装目录中
+	INSTALL_DIR = $(DADK_CURRENT_BUILD_DIR)
+else
+# 如果是在本地编译,那么安装到当前目录下的install目录中
+	INSTALL_DIR = ./install
+endif
+
+ifeq ($(ARCH), x86_64)
+	export RUST_TARGET=x86_64-unknown-linux-musl
+else ifeq ($(ARCH), riscv64)
+	export RUST_TARGET=riscv64gc-unknown-linux-gnu
+else 
+# 默认为x86_86,用于本地编译
+	export RUST_TARGET=x86_64-unknown-linux-musl
+endif
+
+run:
+	RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) run --target $(RUST_TARGET)
+
+build:
+	RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) build --target $(RUST_TARGET)
+
+clean:
+	RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) clean --target $(RUST_TARGET)
+
+test:
+	RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) test --target $(RUST_TARGET)
+
+doc:
+	RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) doc --target $(RUST_TARGET)
+
+fmt:
+	RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) fmt
+
+fmt-check:
+	RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) fmt --check
+
+run-release:
+	RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) run --target $(RUST_TARGET) --release
+
+build-release:
+	RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) build --target $(RUST_TARGET) --release
+
+clean-release:
+	RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) clean --target $(RUST_TARGET) --release
+
+test-release:
+	RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) test --target $(RUST_TARGET) --release
+
+.PHONY: install
+install:
+	RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) install --target $(RUST_TARGET) --path . --no-track --root $(INSTALL_DIR) --force

+ 7 - 0
user/apps/test_lo/README.md

@@ -0,0 +1,7 @@
+# test-lo 
+
+lo网卡功能测试程序
+
+## 测试过程:
+
+通过创建一个UDP套接字,然后发送一条消息到本地回环地址127.0.0.1(lo网卡),再接收并验证这条消息,以此来测试lo网卡的功能。期望发送的消息和接收到的消息是完全一样的。通过日志输出查看测试是否成功。

+ 25 - 0
user/apps/test_lo/src/main.rs

@@ -0,0 +1,25 @@
+use std::net::UdpSocket;
+use std::str;
+
+fn main() -> std::io::Result<()> {
+    let socket = UdpSocket::bind("127.0.0.1:34254")?;
+    socket.connect("127.0.0.1:34254")?;
+
+    let msg = "Hello, loopback!";
+    socket.send(msg.as_bytes())?;
+
+    let mut buf = [0; 1024];
+    let (amt, _src) = socket.recv_from(&mut buf)?;
+
+    let received_msg = str::from_utf8(&buf[..amt]).expect("Could not read buffer as UTF-8");
+
+    println!("Sent: {}", msg);
+    println!("Received: {}", received_msg);
+
+    assert_eq!(
+        msg, received_msg,
+        "The sent and received messages do not match!"
+    );
+
+    Ok(())
+}

+ 27 - 0
user/dadk/config/test_lo_0_1_0.dadk

@@ -0,0 +1,27 @@
+{
+  "name": "test_lo",
+  "version": "0.1.0",
+  "description": "test for lo interface",
+  "rust_target": null,
+  "task_type": {
+    "BuildFromSource": {
+      "Local": {
+        "path": "apps/test_lo"
+      }
+    }
+  },
+  "depends": [],
+  "build": {
+    "build_command": "make install"
+  },
+  "install": {
+    "in_dragonos_path": "/"
+  },
+  "clean": {
+    "clean_command": "make clean"
+  },
+  "envs": [],
+  "build_once": false,
+  "install_once": false,
+  "target_arch": ["x86_64"]
+}