diff --git a/.gitignore b/.gitignore
index 88bd924de63001c779990c541bc345539171f510..b22d33ce09555500c6924f5068bece1f1ae75a92 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,3 @@
 /target
-/user/linker.debug.ld
-/user/linker.ld
+/user/.linkers/*
 /src/app_loader.asm
\ No newline at end of file
diff --git a/Makefile b/Makefile
index 5c217a0bccf74062d015769aca13a6245508f747..fa05d78bbccb5cd102a151d497975f01f4cdbb12 100644
--- a/Makefile
+++ b/Makefile
@@ -10,11 +10,9 @@ KERNEL_BINARY_NAME := $(shell cargo metadata --no-deps --format-version 1 | jq -
 USER_BINARY_NAMES := $(shell cargo metadata --no-deps --format-version 1 | jq -r ' \
   . as $$root | \
   .packages[] | \
-  .targets[] | \
-  select( .kind | map(. == "bin") | any ) | \
-  .name \
-' | grep -v $(KERNEL_BINARY_NAME))
-
+  select(.name == "user") | \
+  .metadata.applications.order[] \
+')
 
 TARGET_DIR := target/riscv64gc-unknown-none-elf
 KERNEL_ELF := $(TARGET_DIR)/debug/$(KERNEL_BINARY_NAME)
diff --git a/src/app_loader.rs b/src/app_loader.rs
new file mode 100644
index 0000000000000000000000000000000000000000..2c873b109a2ed6cf9e11ba75e620c5b7463c5293
--- /dev/null
+++ b/src/app_loader.rs
@@ -0,0 +1,28 @@
+use core::arch::asm;
+
+use crate::{APP_MANAGER, batch::AppManager};
+
+pub fn load_apps() {
+    let app_manager = APP_MANAGER.ref_cell.borrow();
+    for i in 0..app_manager.app_count {
+        load_app(&app_manager, i);
+    }
+    unsafe {
+        asm!("fence.i");
+    }
+}
+
+fn load_app(app_manager: &AppManager, app_id: usize) {
+    let app_start = app_manager.app_table[app_id];
+    let app_end = if app_id + 1 < app_manager.app_count {
+        app_manager.app_table[app_id + 1]
+    } else {
+        app_manager.apps_end
+    };
+    let app_size = app_end - app_start;
+    let app_base = AppManager::get_app_base_addr(app_id);
+    unsafe { core::slice::from_raw_parts_mut(app_base as *mut u8, 0x20000).fill(0) };
+    let app_src = unsafe { core::slice::from_raw_parts(app_start as *const u8, app_size) };
+    let app_dst = unsafe { core::slice::from_raw_parts_mut(app_base as *mut u8, app_size) };
+    app_dst.copy_from_slice(app_src);
+}
diff --git a/src/batch.rs b/src/batch.rs
index 185541d788fa3972271054dba7d35982f136e3b4..0e63de626616f295d636d3295d427e90e2eeaf45 100644
--- a/src/batch.rs
+++ b/src/batch.rs
@@ -4,7 +4,8 @@ use crate::{APP_MANAGER, info, printkln, sbi::sbi_shutdown, trap::context::TrapC
 
 pub const MAX_APP_NUM: usize = 64;
 /// 与 user-build 中 linker.ld 中的 BASE_ADDRESS 保持一致
-pub const APP_BASE_ADDRESS: usize = 0x80400000;
+pub const USER_BASE_ADDRESS: usize = 0x80400000;
+pub const USER_SPACE_SIZE: usize = 0x00200000;
 pub const KERNEL_STACK_SIZE: usize = 0x8000;
 pub const USER_STACK_SIZE: usize = 0x8000;
 
@@ -122,25 +123,8 @@ impl AppManager {
         }
     }
 
-    pub fn load_app(&self, app_id: usize) {
-        let app_start = self.app_table[app_id];
-        let app_end = if app_id + 1 < self.app_count {
-            self.app_table[app_id + 1]
-        } else {
-            self.apps_end
-        };
-        let app_size = app_end - app_start;
-        let app_base = APP_BASE_ADDRESS;
-        unsafe { core::slice::from_raw_parts_mut(APP_BASE_ADDRESS as *mut u8, 0x20000).fill(0) };
-        let app_src = unsafe { core::slice::from_raw_parts(app_start as *const u8, app_size) };
-        let app_dst = unsafe { core::slice::from_raw_parts_mut(app_base as *mut u8, app_size) };
-        app_dst.copy_from_slice(app_src);
-        unsafe {
-            asm!("fence.i");
-        }
-        // let app_entry = app_base;
-        // let app_entry: extern "C" fn() -> ! = unsafe { core::mem::transmute(app_entry) };
-        // app_entry();
+    pub fn get_app_base_addr(app_id: usize) -> usize {
+        USER_BASE_ADDRESS + app_id * USER_SPACE_SIZE
     }
 
     pub fn get_app_name(app_name_ptr: *const i8) -> &'static str {
@@ -157,7 +141,7 @@ impl AppManager {
 
     pub fn print_app_info(&self, app_id: usize) {
         info!(
-            "App {} - Name: {}, Start: {:#x}, End: {:#x}",
+            "App {} - Name: {}, Offset: [{:#x}, {:#x}), Base: {:#x}",
             app_id,
             self.app_name_table[app_id],
             self.app_table[app_id],
@@ -165,7 +149,8 @@ impl AppManager {
                 self.app_table[app_id + 1]
             } else {
                 self.apps_end
-            }
+            },
+            Self::get_app_base_addr(app_id)
         );
     }
 }
@@ -177,28 +162,19 @@ pub fn run_next_app() {
         info!("All apps have been run.");
         sbi_shutdown(false);
     }
-    app_manager.load_app(app_manager.current);
-    app_manager.print_app_info(app_manager.current);
+    let app_id = app_manager.current;
+    app_manager.print_app_info(app_id);
     app_manager.current = app_manager.current + 1;
     drop(app_manager);
     unsafe extern "C" {
         fn __restore_trap(ctx_ptr: usize);
     }
-    // panic!("{}", __restore_trap as usize);
-    printkln!("{:#x}", __restore_trap as usize);
-    printkln!("{:#x}", USER_STACK.top());
     unsafe {
-        let a = KERNEL_STACK.push_ctx(TrapCtx::init_app_context(
-            APP_BASE_ADDRESS,
+        let ctx = KERNEL_STACK.push_ctx(TrapCtx::init_app_context(
+            AppManager::get_app_base_addr(app_id),
             USER_STACK.top(),
         ));
-        // print the context
-        let ctx = &*(a as *const TrapCtx);
-        printkln!("{:#x}", ctx.sstatus);
-        printkln!("{:#x}", ctx.sepc);
-        printkln!("{:#x}", ctx.x[2]);
-
-        __restore_trap(a);
+        __restore_trap(ctx);
     }
     unreachable!();
 }
diff --git a/src/main.rs b/src/main.rs
index b6e2a607748c721599ac379322ad477b95641a31..2befdaf9c12fc70f191b43f0ae0d3f670840ca61 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,6 +2,7 @@
 #![no_main]
 #![feature(inline_const_pat)]
 
+mod app_loader;
 mod batch;
 mod io;
 mod language_item;
@@ -38,6 +39,7 @@ pub extern "C" fn _kernel_entry() -> ! {
     startup_log();
     trap::init();
     APP_MANAGER.ref_cell.borrow().print_apps_info();
+    app_loader::load_apps();
     batch::run_next_app();
     printkln!("Hello, {}!", "World");
 
diff --git a/user-build/src/lib.rs b/user-build/src/lib.rs
index 94bfa8a164b6a3e14fdc6d0841696ce6e5fb8cc1..a2856cae62451305758b949ffb00bade961ec21c 100644
--- a/user-build/src/lib.rs
+++ b/user-build/src/lib.rs
@@ -1,36 +1,115 @@
 use core::include_str;
-pub fn setup_linker(profile: &str) {
-    let linker = format!(
+
+const USER_BASE_ADDRESS: usize = 0x80400000;
+const USER_SPACE_SIZE: usize = 0x00200000;
+
+pub fn setup() {
+    let linker_dir = get_linker_dir();
+    clean_linkers(&linker_dir);
+
+    let current_profile = std::env::var("PROFILE").unwrap_or_else(|_| "debug".to_string());
+    let apps = get_apps_in_order();
+    for profile in &["debug", "release"] {
+        for (app_id, app_name) in apps.iter().enumerate() {
+            setup_linker(profile, app_name, app_id, &linker_dir);
+        }
+    }
+
+    for (app_id, app_name) in apps.iter().enumerate() {
+        println!(
+            "cargo:rustc-link-arg-bin={app_name}=-T{linker_dir}/{linker_name}",
+            app_name = app_name,
+            linker_dir = linker_dir,
+            linker_name = get_linker_name(&app_name, app_id, &current_profile),
+        );
+    }
+
+    println!("cargo:rergun-if-changed=user-build");
+    println!("cargo:rergun-if-changed=user/Cargo.toml");
+    println!("cargo:rustc-force-frame-pointers=yes");
+}
+
+/// 为一个二进制目标文件生成链接脚本,
+/// 生成的链接脚本将会被写入到 `linkers` 目录下
+///
+/// ## 参数
+/// - `profile`:编译配置,`debug` 或 `release`
+/// - `app_name`:二进制目标的名称
+/// - `app_id`:二进制目标的 ID, 由 manifest 中的 `package.metadata.applications.order` 定义
+fn setup_linker(profile: &str, app_name: &str, app_id: usize, linker_dir: &str) {
+    let mut linker = format!(
         r#"/**************************************************************************
 * This is a linker script is generated automatically by the build script. *
 * DO NOT MODIFY IT MANUALLY.                                              *
 ***************************************************************************/
+
+/* Linker script for {app_name} ({app_id}) in {profile} mode. */
+
 {}"#,
-        if profile == "release" {
-            include_str!("linker.ld")
-        } else {
-            include_str!("linker.debug.ld")
-        }
+        include_str!("linker.template.ld")
     );
-    // copy the linker script to the src directory
-    let out_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
-    let linker_path = std::path::Path::new(&out_dir).join(if profile == "release" {
-        "linker.ld"
+
+    /*TEMPLATE {DEBUG-DISCARD}*/
+    if profile == "release" {
+        linker = replace_template(linker, "DEBUG-DISCARD", "*(.debug*)");
     } else {
-        "linker.debug.ld"
-    });
+        linker = replace_template(linker, "DEBUG-DISCARD", "");
+    }
+
+    /*TEMPLATE {base-address}*/
+    let base_address = format!("0x{:08x}", USER_BASE_ADDRESS + app_id * USER_SPACE_SIZE);
+    linker = replace_template(linker, "BASE_ADDRESS", &base_address);
+
+    let linker_path =
+        std::path::Path::new(linker_dir).join(get_linker_name(app_name, app_id, profile));
+
     std::fs::write(&linker_path, linker).unwrap();
 }
 
-pub fn setup() {
-    let profile = std::env::var("PROFILE").unwrap_or_else(|_| "debug".to_string());
-    setup_linker(&profile);
-    if profile == "release" {
-        println!("cargo:rergun-if-changed=linker.ld");
-        println!("cargo:rustc-link-arg=-Tuser/linker.ld");
-    } else {
-        println!("cargo:rerun-if-changed=linker.debug.ld");
-        println!("cargo:rustc-link-arg=-Tuser/linker.debug.ld");
+fn get_linker_dir() -> String {
+    let out_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
+    let linker_dir_path = std::path::Path::new(&out_dir).join(".linkers");
+    linker_dir_path.to_str().unwrap().to_string()
+}
+
+fn get_linker_name(app_name: &str, app_id: usize, profile: &str) -> String {
+    format!("linker-{app_id}-{app_name}-{profile}.ld")
+}
+
+fn replace_template(template: String, key: &str, value: &str) -> String {
+    template.replace(&format!("/*TEMPLATE {{{}}}*/", key), value)
+}
+
+fn clean_linkers(linker_dir: &str) {
+    let linker_dir = std::path::Path::new(linker_dir);
+    assert!(linker_dir.ends_with(".linkers"));
+    assert!(linker_dir.starts_with(std::env::var("CARGO_MANIFEST_DIR").unwrap()));
+    std::fs::create_dir_all(linker_dir).unwrap();
+    for entry in std::fs::read_dir(linker_dir).unwrap() {
+        let entry = entry.unwrap();
+        let path = entry.path();
+        if path.is_file() {
+            std::fs::remove_file(path).unwrap();
+        }
     }
-    println!("cargo:rustc-force-frame-pointers=yes");
+}
+
+pub fn get_apps_in_order() -> Vec<String> {
+    // 应用程序顺序已在 user/Cargo.toml package.metadata.applications.order 中定义
+    // Make 会获取该值
+    let user_root = std::env::var("CARGO_MANIFEST_DIR").unwrap();
+    let user_root = user_root.split("/").collect::<Vec<_>>();
+    // remove the last part of the path
+    let workspace_root = user_root[..user_root.len() - 1].join("/");
+    let make_args = std::process::Command::new("make")
+        .current_dir(workspace_root)
+        .arg("echo-make-args")
+        .arg("GET_MAKE_ARG=USER_BINARY_NAMES")
+        .arg("--no-print-directory")
+        .output()
+        .unwrap()
+        .stdout;
+    let make_args = std::str::from_utf8(&make_args).unwrap();
+    let make_args = make_args.trim().split(' ').map(|s| s.to_string());
+    make_args.collect::<Vec<_>>()
 }
diff --git a/user-build/src/linker.ld b/user-build/src/linker.ld
deleted file mode 100644
index 1dc9acc66ddbfc66ec8bdc0ff3a7d26323a80816..0000000000000000000000000000000000000000
--- a/user-build/src/linker.ld
+++ /dev/null
@@ -1,48 +0,0 @@
-/* linker for app */
-OUTPUT_ARCH(riscv)
-ENTRY(_start)
-BASE_ADDRESS = 0x80400000;
-
-SECTIONS
-{
-    /* put entry at the start */
-    . = BASE_ADDRESS;
-    __app_start = .;
-    __text_start = .;
-    .text : {
-        *(.text.entry)
-        *(.text .text.*)
-    }
-
-    __text_end = .;
-
-    __rodata_start = .;
-    .rodata : {
-        *(.rodata .rodata.*)
-        *(.srodata .srodata.*)
-    }
-
-    __rodata_end = .;
-
-    __data_start = .;
-    .data : {
-        *(.data .data.*)
-        *(.sdata .sdata.*)
-    }
-
-    __data_end = .;
-
-    .bss : {
-        __bss_start = .;
-        *(.bss .bss.*)
-        *(.sbss .sbss.*)
-    }
-
-    __bss_end = .;
-    __app_end = .;
-
-    /DISCARD/ : {
-        *(.eh_frame)
-        *(.debug*)
-    }
-}
\ No newline at end of file
diff --git a/user-build/src/linker.debug.ld b/user-build/src/linker.template.ld
similarity index 88%
rename from user-build/src/linker.debug.ld
rename to user-build/src/linker.template.ld
index e35984665cb05cdbbef76e6db289d4a6d78b0121..0e8a54b31abd25d5147a01b32f1c881b8b197d41 100644
--- a/user-build/src/linker.debug.ld
+++ b/user-build/src/linker.template.ld
@@ -1,7 +1,6 @@
-/* linker for app, debug version */
 OUTPUT_ARCH(riscv)
 ENTRY(_start)
-BASE_ADDRESS = 0x80400000;
+BASE_ADDRESS = /*TEMPLATE {BASE_ADDRESS}*/;
 
 SECTIONS
 {
@@ -43,5 +42,6 @@ SECTIONS
 
     /DISCARD/ : {
         *(.eh_frame)
+        /*TEMPLATE {DEBUG-DISCARD}*/
     }
 }
\ No newline at end of file
diff --git a/user/Cargo.toml b/user/Cargo.toml
index 9885344ed865a339654cfda6d6cf6709fef0399e..1f8b3d1253fba8ba8af0945954178fd2a0cf963d 100644
--- a/user/Cargo.toml
+++ b/user/Cargo.toml
@@ -17,3 +17,7 @@ lib = { path = "../user-lib" }
 
 [build-dependencies]
 build = { path = "../user-build" }
+
+[package.metadata.applications]
+# 应用程序顺序
+order = ["hello", "bye"]
\ No newline at end of file