插件化开发入门

news2024/10/5 13:09:50

一、背景

顾名思义,插件化开发就是将某个功能代码封装为一个插件模块,通过插件中心的配置来下载、激活、禁用、或者卸载,主程序无需再次重启即可获取新的功能,从而实现快速集成。当然,实现这样的效果,必须遵守一些插件接口的标准,不能与已有的功能冲突。目前能支持插件化开发的成熟框架很多,但本文仅从思路的实现角度,从0到1实现简单的插件化开发框架。

二、实现思路

思路:定义插件接口 -> 实现插件接口 -> 通过反射机制加载插件 -> 调用插件方法。

开发语言:支持反射机制的所有高级语言均可实现插件式开发,或有 FFI 调用 Native 函数的编程语言。

三、Java 通过反射机制实现插件化开发

1、创建插件接口

定义插件接口:一个执行方法

package service;

/**
 * 通用插件接口
 *
 * @author yushanma
 * @since 2023/3/5 16:36
 */
public interface IPluginService {
    /**
     * 执行插件
     */
    public void run();
}

2、实现插件接口

package impl;
import service.IPluginService;

/**
 * 打印插件
 *
 * @author yushanma
 * @since 2023/3/5 16:37
 */
public class MyPrinterPlugin implements IPluginService {

    @Override
    public void run() {
        System.out.println("执行插件方法...");
    }
}

3、插件中心

管理与加载插件。

Step 1、插件实体类封装

package entity;

import lombok.Data;

/**
 * 插件实体类
 *
 * @author yushanma
 * @since 2023/3/5 16:44
 */
@Data
public class PluginEntity {
    /**
     * 插件名
     */
    private String pluginName;

    /**
     * 插件路径
     */
    private String jarPath;

    /**
     * 字节码名字
     */
    private String className;
}

需要获取插件名、插件实现的Jar包路径、字节码路径

Step 2、通过反射机制实现插件实例化

package loader;

import entity.PluginEntity;
import exception.PluginException;
import lombok.Data;
import lombok.NoArgsConstructor;
import service.IPluginService;

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 插件管理器
 *
 * @author yushanma
 * @since 2023/3/5 16:44
 */
@Data
@NoArgsConstructor
public class PluginManager {
    private Map<String, Class<?>> clazzMap = new HashMap<>();

    public PluginManager(List<PluginEntity> plugins) throws PluginException {
        initPlugins(plugins);
    }

    public void initPlugin(PluginEntity plugin) throws PluginException {
        try {
            //URL url = new URL("file:" + plugin.getJarPath());
            URL url = new File(plugin.getJarPath()).toURI().toURL();
            URLClassLoader classLoader = new URLClassLoader(new URL[]{url});
            Class<?> clazz = classLoader.loadClass(plugin.getClassName());
            clazzMap.put(plugin.getClassName(), clazz);
        } catch (Exception e) {
            throw new PluginException("plugin " + plugin.getPluginName() + " init error: >>> " + e.getMessage());
        }
    }

    public void initPlugins(List<PluginEntity> plugins) throws PluginException {
        for (PluginEntity plugin : plugins) {
            initPlugin(plugin);
        }
    }

    public IPluginService getInstance(String className) throws PluginException {
        Class<?> clazz = clazzMap.get(className);
        Object instance = null;
        try {
            instance = clazz.newInstance();
        } catch (Exception e) {
            throw new PluginException("plugin " + className + " instantiate error," + e.getMessage());
        }
        return (IPluginService) instance;
    }
}

Step 3、通过 XML 文件来配置管理插件

        <dependency>
            <groupId>org.dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>2.1.1</version>
        </dependency>
package conf;

import entity.PluginEntity;
import exception.PluginException;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

/**
 * 解析 XML 插件配置
 *
 * @author yushanma
 * @since 2023/3/5 16:44
 */
public class PluginXmlParser {

    public static List<PluginEntity> getPluginList() throws PluginException {

        List<PluginEntity> list = new ArrayList<>();

        SAXReader saxReader = new SAXReader();
        Document document = null;
        try {
            document = saxReader.read(new File("src/main/resources/plugin.xml"));
        } catch (Exception e) {
            throw new PluginException("read plugin.xml error," + e.getMessage());
        }
        Element root = document.getRootElement();
        List<?> plugins = root.elements("plugin");
        for (Object pluginObj : plugins) {
            Element pluginEle = (Element) pluginObj;
            PluginEntity plugin = new PluginEntity();
            plugin.setPluginName(pluginEle.elementText("name"));
            plugin.setJarPath(pluginEle.elementText("jar"));
            plugin.setClassName(pluginEle.elementText("class"));
            list.add(plugin);
        }
        return list;
    }

}
<!-- plugin.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<plugins>
    <plugin>
        <name>测试插件</name>
        <jar>plugins/PrinterPlugin-1.0-SNAPSHOT.jar</jar>
        <class>impl.MyPrinterPlugin</class>
    </plugin>
    <plugin>
        <name>测试插件</name>
        <jar>plugins/PrinterPlugin-1.0-SNAPSHOT.jar</jar>
        <class>impl.MyPrinterPlugin</class>
    </plugin>
</plugins>

Step 4、解析 XML 文件并加载插件

package loader;

import conf.PluginXmlParser;
import entity.PluginEntity;
import exception.PluginException;
import service.IPluginService;

import java.util.List;

/**
 * 插件加载器
 *
 * @author yushanma
 * @since 2023/3/5 16:44
 */
public class PluginLoader {

    public void run() throws PluginException {
        // 从配置文件加载插件
        List<PluginEntity> pluginList = PluginXmlParser.getPluginList();
        PluginManager pluginManager = new PluginManager(pluginList);

        for (PluginEntity plugin : pluginList) {
            IPluginService pluginService = pluginManager.getInstance(plugin.getClassName());
            System.out.println("开始执行[" + plugin.getPluginName() + "]插件...");
            // 调用插件
            pluginService.run();
            System.out.println("[" + plugin.getPluginName() + "]插件执行完成");
        }

        // 动态加载插件
//        PluginEntity plugin = new PluginEntity();
//        plugin.setPluginName("");
//        plugin.setJarPath("");
//        plugin.setClassName("");
//        pluginManager.initPlugin(plugin);
//        IPluginService pluginService = pluginManager.getInstance("");
//        pluginService.run();
    }
}

4、测试效果

import exception.PluginException;
import loader.PluginLoader;

/**
 * desc
 *
 * @author yushanma
 * @since 2023/3/5 16:44
 */
public class DemoMain {
    public static void main(String[] args) throws PluginException {
        PluginLoader loader = new PluginLoader();
        loader.run();
    }
}

四、Rust 通过 libloader 库实现插件化开发

通过 libloader 库可以调用动态链接库函数,需要 FFI 支持。

Step 1、创建 lib

cargo new --lib mydll
// 有参数没有返回值
#[no_mangle]
pub fn println(str: &str) {
    println!("{}", str);
}

// 有参数有返回值
#[no_mangle]
pub fn add(a: usize, b: usize) -> usize {
    a + b
}

// 没有参数没有返回值
#[no_mangle]
pub fn print_hello() {
    println!("Hello");
}

// 字符串类型
#[no_mangle]
pub fn return_str(s1: &str) -> &str{
    s1
}

Step 2、toml 配置编译类型

[package]
name = "mydll"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]


# rlib:Rust库,这是cargo new默认的种类,只能被Rust调用;
# dylib:Rust规范的动态链接库,windows上编译成.dll,linux上编译成.so,也只能被Rust调用;
# cdylib:满足C语言规范的动态链接库,windows上编译成.dll,linux上编译成.so,可以被其他语言调用
# staticlib:静态库,windows上编译成.lib,linux上编译成.a,可以被其他语言调用

[lib]
crate-type = ["cdylib"]

Step 3、编译为 dll

cargo build

可以看到,所有的函数都被正常导出,具体原理请参考:https://fasterthanli.me/articles/so-you-want-to-live-reload-rust

Step 4、动态加载 dll

use cstr::cstr;
use libloader::*;
use std::{ffi::CStr,os::raw::c_char};

fn main() {
    get_libfn!("dll/mydll.dll", "println", println, (), s: &str);
    println("你好");

    get_libfn!("dll/mydll.dll", "add", add, usize, a: usize, b: usize);
    println!(" 1 + 2 = {}", add(1, 2));

    get_libfn!("dll/mydll.dll", "print_hello", print_hello, bool);
    print_hello();

    get_libfn!("dll/mydll.dll","return_str", return_str,*const c_char, s: *const c_char);
    let str = unsafe { CStr::from_ptr(return_str(cstr!("你好 ").as_ptr())) };
    print!("out {}", str.to_str().unwrap());
}

五、C# 通过反射机制实现插件化开发

Step 1、定义插件接口

namespace PluginInterface
{

    public interface IPlugin
    {
        // 获取插件名字
        public string GetName();

        // 获取插件所提供的功能列表
        public string[] GetFunction();

        // 执行插件某个功能
        public bool Execute(string fn);
    }

}

Step 2、实现插件接口

using PluginInterface;
using System;
using System.Linq;

namespace MyPlugin
{

    public class PrinterPlugin : IPlugin
    {
        private static readonly string PLUGIN_NAME = "PrinterPlugin";

        // 获取插件名字
        public string GetName()
        {
            return PLUGIN_NAME;
        }

        // 获取插件所提供的功能列表
        public string[] GetFunction()
        {
            return PrinterFunc.FuncDics.Keys.ToArray();
        }

        // 执行插件某个功能
        public bool Execute(string fn)
        {
            return PrinterFunc.Run(fn);
        }

        // 传参功能
        public static object PrintLabel(string sn)
        {
            Console.WriteLine($"打印标签{sn}...DONE");
            return true;
        }
    }
}
using System;
using System.Collections.Generic;

namespace MyPlugin
{
    // 封装打印机支持的功能
    internal class PrinterFunc
    {
        // 功能字典
        public static Dictionary<string, Func<bool>> FuncDics = new Dictionary<string, Func<bool>>
        {
            {"PrintPhoto",PrintPhoto },
            {"PrintDoc",PrintDoc }
        };
        // 执行某个功能
        public static bool Run(string name)
        {
            if (!FuncDics.ContainsKey(name))
            {
                return false;
            }

            return (bool)FuncDics[name].Invoke();
        }
        // 打印照片
        public static bool PrintPhoto()
        {
            Console.WriteLine("打印照片...DONE");
            return true;
        }
        // 打印文档
        public static bool PrintDoc()
        {
            Console.WriteLine("打印文档...DONE");
            return true;
        }
    }

}

Step 3、通过反射实例化插件

using PluginInterface;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;

namespace CLI.Loader
{
    public class PluginLoader
    {
        // 初始化时加载插件
        public PluginLoader()
        {
            LoadPlugin();
        }

        public Dictionary<string, IPlugin> ListName = new Dictionary<string, IPlugin>();

        // 加载所有插件
        public void LoadPlugin()
        {
            try
            {
                // 清除所有插件缓存
                ListName.Clear();
                // 插件文件夹
                string fileName = "D:\\AwsomeWorkSpace\\CLI\\Plugins\\net5.0\\";
                // 获取所有插件文件
                DirectoryInfo info = new DirectoryInfo(fileName);
                FileInfo[] files = info.GetFiles();
                foreach (FileInfo file in files)
                {
                    if (!file.FullName.EndsWith(".dll"))
                    {
                        continue;
                    }
                    // 通过反射机制创建插件实例
                    Assembly assembly = Assembly.LoadFile(file.FullName);
                    Type[] types = assembly.GetTypes();
                    foreach (Type type in types)
                    {
                        // 如果某些类实现了预定义的插件接口,则认为该类适配与主程序(是主程序的插件)

                        if (type.GetInterface("IPlugin") != null)
                        {
                            // 创建该类实例
                            IPlugin plugin = assembly.CreateInstance(type.FullName) as IPlugin;
                            if (plugin == null)
                            {
                                throw new Exception("插件错误");
                            }
                            ListName.Add(plugin.GetName(), plugin);
                            // 调用插件的某个传参方法
                            MethodInfo printLabel = type.GetMethod("PrintLabel");
                            object res = printLabel.Invoke(plugin, parameters: new object[] { "HQ31122222222222" });
                            Console.WriteLine(res?.ToString());
                            // 调用插件内部的 Execute 方法
                            MethodInfo execute = type.GetMethod("Execute");
                            res = execute.Invoke(plugin, parameters: new object[] { "PrintPhoto" });
                            Console.WriteLine(res?.ToString());
                            res = execute.Invoke(plugin, parameters: new object[] { "PrintDoc" });
                            Console.WriteLine(res?.ToString());
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }

        // 插件启动
        public void Start()
        {
            Console.WriteLine("==== 插件中心 ====");
            Console.WriteLine("1--加载插件列表");
            Console.WriteLine("2--重新刷新插件");
            int switchVal = int.Parse(Console.ReadLine());
            switch (switchVal)
            {
                case 1:
                    GetPluginList();
                    break;
                case 2:
                    LoadPlugin();
                    break; ;
            }

        }

        // 加载插件列表
        public void GetPluginList()
        {
            Console.WriteLine("--------插件列表--------");
            foreach (var VARIABLE in ListName.Keys)
            {
                Console.WriteLine($"----{VARIABLE}");
            }
            Console.WriteLine("--------请输入插件名--------");
            GetPluginFunc(Console.ReadLine());
        }

        // 加载插件功能
        public void GetPluginFunc(string pluginName)
        {
            if (!ListName.ContainsKey(pluginName))
            {
                return;
            }
            IPlugin plugin = ListName[pluginName];
            string[] funcList = plugin.GetFunction();
            for (int i = 0; i < funcList.Length; i++)
            {
                Console.WriteLine(funcList[i]);
                plugin.Execute(funcList[i]);
            }
        }


    }
}

ok,可以看到,插件化开发的实现并不复杂,但是其中用到的反射机制会消耗部分性能,并且 dll 也会存在一些逆向工程或者反向注入等信安问题,需要谨慎使用。当然,框架的完善更是任重道远的过程。

六、.NET 6/7 导出非托管函数能力

环境:Visual Studio 2022 / .NET7

参考:https://github.com/dotnet/runtime/tree/main/src/coreclr/nativeaot/docs

Step 1、创建类库项目

dotnet new classlib -o mydll -f net6.0

Step 2、配置 AOT Native

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net7.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <PublishAot>true</PublishAot>
  </PropertyGroup>

</Project>

Step 3、导出非托管函数

using System.Runtime.InteropServices;
using Seagull.BarTender.Print;

namespace ClassLibrary1
{
    public class Class1
    {
        // 无参数有返回值
        [UnmanagedCallersOnly(EntryPoint = "IsOk")]
        public static bool IsOk()
        {
            return true;
        }
        // 有参数无返回值
        [UnmanagedCallersOnly(EntryPoint = "MyPrinter")]
        public static void MyPrinter(IntPtr pString)
        {

            try
            {
                if (pString != IntPtr.Zero)
                {
                    string str = new(Marshal.PtrToStringAnsi(pString));
                    Console.WriteLine(str);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(">>> Exception " + e.Message);
            }
        }
        // 有参数有返回值
        [UnmanagedCallersOnly(EntryPoint = "MyConcat")]
        public static IntPtr MyConcat(IntPtr pString1, IntPtr pString2)
        {
            string concat = "";

            try
            {
                if (pString1 != IntPtr.Zero && pString2 != IntPtr.Zero)
                {
                    string str1 = new(Marshal.PtrToStringAnsi(pString1));
                    string str2 = new(Marshal.PtrToStringAnsi(pString2));
                    concat = string.Concat(str1, str2);
                }
            }
            catch (Exception e)
            {
                concat = e.Message;
            }
            return Marshal.StringToHGlobalAnsi(concat);
        }
        // 无参数无返回值
        [UnmanagedCallersOnly(EntryPoint = "PrintHello")]
        public static void PrintHello()
        {
            Console.WriteLine(">>> Hello");
        }
    }
}

Step 4、查看导出结果

dotnet publish /p:NativeLib=Shared /p:SelfContained=true -r win-x64 -c release

可以看到 native 、publish 文件夹,里面的 dll 文件

函数正常导出,最后一个是默认导出的函数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/390178.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【博学谷学习记录】超强总结,用心分享丨人工智能 自然语言处理 文本特征处理小结

目录文本特征处理作用常见的文本特征处理方法添加n-gram特征说明提取n-gram文本长度规范说明实现导包问题记录心得文本特征处理作用 文本特征处理包括为语料添加具有普适性的文本特征, 如:n-gram特征 以及对加入特征之后的文本语料进行必要的处理, 如: 长度规范. 这些特征处…

vue3的插槽slots

文章目录普通插槽Test.vueFancyButton.vue具名插槽Test.vueBaseLayout.vue作用域插槽默认插槽Test.vueBaseLayout.vue具名作用域插槽Test.vueBaseLayout.vue普通插槽 父组件使用子组件时&#xff0c;在子组件闭合标签中提供内容模板&#xff0c;插入到子组件定义的出口的地方 …

云桌面技术初识:VDI,IDV,VOI,RDS

VDI&#xff08;Virtual Desktop Infrastucture&#xff0c;虚拟桌面架构&#xff09;&#xff0c;俗称虚拟云桌面 VDI构架采用的“集中存储、集中运算”构架&#xff0c;所有的桌面以虚拟机的方式运行在服务器硬件虚拟化层上&#xff0c;桌面以图像传输的方式发送到客户端。 …

序列化与反序列化概念

序列化是指将对象的状态信息转换为可以存储或传输的形式的过程。 在Java中创建的对象&#xff0c;只要没有被回收就可以被复用&#xff0c;但是&#xff0c;创建的这些对象都是存在于JVM的堆内存中&#xff0c;JVM处于运行状态时候&#xff0c;这些对象可以复用&#xff0c; 但…

taobao.item.delete( 删除单条商品 )

&#xffe5;开放平台免费API必须用户授权 删除单条商品 公共参数 请求地址: HTTP地址 http://gw.api.taobao.com/router/rest 公共请求参数: 公共响应参数: 请求参数 响应参数 点击获取key和secret 请求示例 TaobaoClient client new DefaultTaobaoClient(url, appkey, s…

【深一点学习】自己实现一下卷积和池化操作,理解超参数意义,理清数学计算方式

二维卷积层 卷积神经网络&#xff08;convolutional neural network&#xff09;是含有卷积层&#xff08;convolutional layer&#xff09;的神经网络。本章中介绍的卷积神经网络均使用最常见的二维卷积层。它有高和宽两个空间维度&#xff0c;常用来处理图像数据。 二维互相…

Python、JavaScript、C、C++和Java可视化代码执行工具

Python、JavaScrip、C、C和Java可视化代码执行工具 该工具通过可视化代码执行来帮助您学习Python、JavaScript、C、C和Java编程。可在可视化区动态展示执行过程中的调用栈、相关变量以及对应的变量值。https://pythontutor.com/ 下面以执行下面python这段代码为例 class MyCla…

9万字“联、管、用”三位一体雪亮工程整体建设方案

本资料来源公开网络&#xff0c;仅供个人学习&#xff0c;请勿商用。部分资料内容&#xff1a; 1、 总体设计方案 围绕《公共安全视频监控建设联网应用”十三五”规划方案》中的总体架构和一总两分结构要求的基础上&#xff0c;项目将以“加强社会公共安全管理&#xff0c;提高…

leetcode打卡-贪心算法

455.分发饼干 leetcode题目链接&#xff1a;https://leetcode.cn/problems/assign-cookies leetcode AC记录&#xff1a; 代码如下&#xff1a; public int findContentChildren(int[] g, int[] s) {Arrays.sort(g);Arrays.sort(s);int res 0;int sIndex 0;int gIndex 0…

Kafka生产者的粘性分区算法

分区算法分类 kafka在生产者投递消息时&#xff0c;会根据是否有key采取不用策略来获取分区。 存在key时会根据key计算一个hash值&#xff0c;然后采用hash%分区数的方式获取对应的分区。 而不存在key时采用随机算法选取分区&#xff0c;然后将所有的消息封装到这个batch上直…

2023/3/5 Vue学习笔记 - 生命周期函数探究-2

1 beforeCreated 在组件实例初始化完成之后立即调用。会在实例初始化完成、props 解析之后、data() 和 computed 等选项处理之前立即调用。 组件的组件实例初始化动作&#xff1a;初始化一个空的Vue实例对象&#xff0c;此时&#xff0c;这个对象身上只有一个默认的声明周期函…

Eureka注册中心快速入门

一、提供者与消费者**服务提供者&#xff1a;**一次业务中&#xff0c;被其他微服务调用的服务。&#xff08;提供接口给其他微服务&#xff09;**服务消费者&#xff1a;**一次业务中&#xff0c;调用其他微服务的服务。&#xff08;调用其它微服务提供的接口&#xff09;比如…

如何分辨on-policy和off-policy

on-policy的定义&#xff1a;behavior policy和target-policy相同的是on-policy&#xff0c;不同的是off-policy。 behavior policy&#xff1a;采样数据的策略&#xff0c;影响的是采样出来s,a的分布。 target policy&#xff1a;就是被不断迭代修改的策略。 如果是基于深度…

JavaSE学习笔记总结day18(完结!!!)

今日内容 零、 复习昨日 一、作业 二、进程与线程 三、创建线程 四、线程的API 五、线程状态 六、线程同步 零、 复习昨日 晨考 一、作业 见答案 二、进程与线程[了解] 一个进程就是一个应用程序,进程包含线程 一个进程至少包含一个线程,大部分都是有多条线程在执行任务(多线…

Win系统蓝牙设备频繁卡顿/断连 - 解决方案

Win系统蓝牙设备频繁卡顿/断连 - 解决方案前言常见网卡Intel无线网卡&#xff08;推荐&#xff09;Realtek无线网卡总结查看本机网卡解决方案更新驱动更换网卡&#xff08;推荐&#xff09;前言 无线网卡有2个模块&#xff0c;一个是WiFi&#xff0c;一个是蓝牙&#xff0c;因…

Kubernetes之存储管理(下)

动态卷供应 上篇文章讲述的持久性存储&#xff0c;是先创建pv&#xff0c;然后才能创建pvc。如果不同的命名空间里同时要创建不同的pvc&#xff0c;那么就需要提前创建好pv&#xff0c;这样才能为pvc提供存储。但是这种方式太过繁琐&#xff0c;可以使用storageClass&#xff…

yolov5算法,训练模型,模型检测

嘟嘟嘟嘟&#xff01;工作需要&#xff0c;所以学习了下yolov5算法。是干什么的呢&#xff1f; 通俗来说&#xff0c;可以将它看做是一个小孩儿&#xff0c;通过成年人&#xff08;开发人员&#xff09;提供的大量图片的学习&#xff0c;让自己知道我看到的哪些场景需要提醒给成…

MySQL底层存储B-Tree和B+Tree原理分析

1.B-Tree的原理分析 &#xff08;1&#xff09;什么是B-Tree B-树&#xff0c;全称是 Balanced Tree&#xff0c;是一种多路平衡查找树。 一个节点包括多个key (数量看业务)&#xff0c;具有M阶的B树&#xff0c;每个节点最多有M-1个Key。 节点的key元素个数就是指这个节点能…

Andorid:关于Binder几个面试问题

1.简单介绍下binderbinder是一种进程间通讯的机制进程间通讯需要了解用户空间和内核空间每个进程拥有自己的独立虚拟机&#xff0c;系统为他们分配的地址空间都是互相隔离的。如两个进程需要进行通讯&#xff0c;则需要使用到内核空间做载体&#xff0c;内核空间是所有进程共享…

FL2440(S3C2440A 芯片) 开发板开发笔记

FL2440(S3C2440A 芯片) 开发板开发笔记 开发板的拨码开关指南&#xff1a; FL2440 改 vnfg 飞凌嵌入式 www. witech. com. cn 09. 8. 22 1 开发板使用手册 version4. 0 FL2440 保定飞凌嵌入式技术有限公司 网站&#xff1a;http: //www. witech. com. cn http: //www. he…