linux UIO驱动实践

2023-05-16

linux UIO驱动实践

  • 环境搭建
  • platform 设备驱动
  • UIO驱动

环境搭建

Ubuntu20地址
虚拟机安装与配置见博客开头:驱动虚拟环境搭建记录

一直以为用镜像直接安装的Ubuntu没有内核源码,不能用来编译驱动,只能由源码编译内核后切换内核才能进行驱动的编译,没想到一安装完就可以编译了,错误的印象。
用hello world测试环境是否搭建成功

#include <linux/init.h>
#include <linux/module.h>

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Hcamal");

int hello_init(void)
{
    printk(KERN_INFO "Hello World\n");
    return 0;
}

void hello_exit(void)
{
    printk(KERN_INFO "Goodbye World\n");
}

module_init(hello_init);
module_exit(hello_exit);
obj-m+=hello.o
all:
	make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modules
clean:
	make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) clean

platform 设备驱动

本来想直接编译运行Linux UIO驱动实例介绍的代码看一下效果,但遇到了一些问题:
Skipping BTF generation for /root/driver/test/hello.ko due to unavailability of vmlinux

移动/sys/kernel/btf/vmlinux至/lib/modules/$(shell uname -r)/build/目录
Skipping BTF generation xxx. due to unavailability of vmlinux on Ubuntu 21.04

[ 6657.847233] hello: Unknown symbol __uio_register_device (err -2)
[ 6657.847260] hello: Unknown symbol uio_unregister_device (err -2)

运行modprobe uio
安装驱动错误 Unknown symbol __uio_register_device (err -2)

Driver ‘test’ needs updating - please use bus_type methods
在这里插入图片描述
linux设备驱动(3)devive_driver 详解

想了想,还是自己写一个简单的demo,加深一下印象。由于没有实际的物理设备,为了触发驱动的探测函数,就需要借助platform设备与platform驱动。推荐博客:
Linux 设备驱动开发 —— platform 设备驱动
Linux platform device driver and design
platform_driver——dm9000.c
platform_device——board-dm355-evm.c

hello world demo如下:

#include <linux/module.h>
#include <linux/platform_device.h>
#define DRV_NAME "test"
static int drv_probe(struct platform_device *pdev) {
  printk("call probe function dev name is:%s\n", pdev->name);
  return 0;
}

static int drv_remove(struct platform_device *pdev) {
  printk("call remove function dev name is:%s\n", pdev->name);
  return 0;
}
static struct platform_device *test_device;
static struct platform_driver test_driver = {
    .driver =
        {
            .name = DRV_NAME,
        },
    .probe = drv_probe,
    .remove = drv_remove,
};

static int __init test_init(void) {
  test_device = platform_device_register_simple(DRV_NAME, -1, NULL, 0);
  return platform_driver_register(&test_driver);
}

static void __exit test_exit(void) {
  platform_device_unregister(test_device);
  platform_driver_unregister(&test_driver);
}

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("test driver");
module_init(test_init);
module_exit(test_exit);

在这里插入图片描述
相关函数与结构体

struct platform_device {
	const char	*name;
	int		id;
	bool		id_auto;
	struct device	dev;
	u64		platform_dma_mask;
	struct device_dma_parameters dma_parms;
	u32		num_resources;
	struct resource	*resource;

	const struct platform_device_id	*id_entry;
	/*
	 * Driver name to force a match.  Do not set directly, because core
	 * frees it.  Use driver_set_override() to set or clear it.
	 */
	const char *driver_override;

	/* MFD cell pointer */
	struct mfd_cell *mfd_cell;

	/* arch specific additions */
	struct pdev_archdata	archdata;
};

struct platform_driver {
	int (*probe)(struct platform_device *);
	int (*remove)(struct platform_device *);
	void (*shutdown)(struct platform_device *);
	int (*suspend)(struct platform_device *, pm_message_t state);
	int (*resume)(struct platform_device *);
	struct device_driver driver;
	const struct platform_device_id *id_table;
	bool prevent_deferred_probe;
	/*
	 * For most device drivers, no need to care about this flag as long as
	 * all DMAs are handled through the kernel DMA API. For some special
	 * ones, for example VFIO drivers, they know how to manage the DMA
	 * themselves and set this flag so that the IOMMU layer will allow them
	 * to setup and manage their own I/O address space.
	 */
	bool driver_managed_dma;
};

/**
 * platform_device_register_simple - add a platform-level device and its resources
 * @name: base name of the device we're adding
 * @id: instance id
 * @res: set of resources that needs to be allocated for the device
 * @num: number of resources
 *
 * This function creates a simple platform device that requires minimal
 * resource and memory management. Canned release function freeing memory
 * allocated for the device allows drivers using such devices to be
 * unloaded without waiting for the last reference to the device to be
 * dropped.
 *
 * This interface is primarily intended for use with legacy drivers which
 * probe hardware directly.  Because such drivers create sysfs device nodes
 * themselves, rather than letting system infrastructure handle such device
 * enumeration tasks, they don't fully conform to the Linux driver model.
 * In particular, when such drivers are built as modules, they can't be
 * "hotplugged".
 *
 * Returns &struct platform_device pointer on success, or ERR_PTR() on error.
 */
static inline struct platform_device *platform_device_register_simple(
		const char *name, int id,
		const struct resource *res, unsigned int num)
{
	return platform_device_register_resndata(NULL, name, id,
			res, num, NULL, 0);
}

UIO驱动

代码主体仍然来自于Linux UIO驱动实例介绍,只不过为了简洁,忽略错误处理代码
驱动代码

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/uio_driver.h>
#include <linux/slab.h>

#define DRV_NAME "test"
#define MEMORY_SIZE (1 << 12)

struct uio_info test_info = {
    .name = "test_uio",
    .version = "1.0",
    .irq = UIO_IRQ_NONE,
};

static int drv_probe(struct platform_device *pdev) {
  printk("call probe function dev name is:%s\n", pdev->name);
  struct device *dev = &pdev->dev;
  void *p = kmalloc(MEMORY_SIZE, GFP_KERNEL);  // 申请内存空间
  strcpy(p, "123456");                         // 写入一些数据
  test_info.mem[0].name = "area1";
  test_info.mem[0].addr = (unsigned long)p;
  test_info.mem[0].memtype = UIO_MEM_LOGICAL;
  test_info.mem[0].size = MEMORY_SIZE;
  uio_register_device(dev, &test_info);
  return 0;
}

static int drv_remove(struct platform_device *pdev) {
  printk("call remove function dev name is:%s\n", pdev->name);
  printk("memory data:%s\n", test_info.mem[0].addr);  // 输出内存数据
  uio_unregister_device(&test_info);
  return 0;
}
static struct platform_device *test_device;
static struct platform_driver test_driver = {
    .driver =
        {
            .name = DRV_NAME,
        },
    .probe = drv_probe,
    .remove = drv_remove,
};

static int __init test_init(void) {
  test_device = platform_device_register_simple(DRV_NAME, -1, NULL, 0);
  return platform_driver_register(&test_driver);
}

static void __exit test_exit(void) {
  platform_device_unregister(test_device);
  platform_driver_unregister(&test_driver);
}

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("test driver");
module_init(test_init);
module_exit(test_exit);

相较于platform的demo,只是加入了一些uio_info相关的代码,函数构成一致。
用户程序

#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>
#include <errno.h>

#define UIO_DEV "/dev/uio0"
#define UIO_ADDR "/sys/class/uio/uio0/maps/map0/addr"
#define UIO_SIZE "/sys/class/uio/uio0/maps/map0/size"

int main(void) {
  int uio_fd, addr_fd, size_fd;
  char uio_addr_buf[16], uio_size_buf[16];
  int uio_size;
  void *uio_addr, *access_address;
  uio_fd = open(UIO_DEV, O_RDWR);
  addr_fd = open(UIO_ADDR, O_RDONLY);  // 起始地址
  size_fd = open(UIO_SIZE, O_RDONLY);  // 内存大小
  read(addr_fd, uio_addr_buf, sizeof(uio_addr_buf));
  read(size_fd, uio_size_buf, sizeof(uio_size_buf));
  uio_addr = (void *)strtoul(uio_addr_buf, NULL, 0);
  uio_size = (int)strtol(uio_size_buf, NULL, 0);
  access_address = mmap(NULL, uio_size, PROT_READ | PROT_WRITE, MAP_SHARED, uio_fd, 0);  // 进行mmap映射
  printf("memory data:%s\n", access_address);                                            // 读取内存数据
  strcpy(access_address, "00000000");                                                    // 写入内存数据
  printf("memory data:%s\n", access_address);                                            // 再次读取内存数据
  return 0;
}

用户代码首先读取内存数据,而后写入一些数据,驱动在设备移除函数中读取内存数据,验证用户程序是否成功写入

驱动在drv_probe中写入123456
用户程序读取内存,得到123456
用户程序写入00000000
驱动在drv_remove中读到00000000

在这里插入图片描述

root@driver-virtual-machine ~/d/test# tree /sys/class/uio/uio0/
/sys/class/uio/uio0/
├── dev
├── device -> ../../../test
├── event
├── maps
│   └── map0
│       ├── addr
│       ├── name
│       ├── offset
│       └── size
├── name
├── power
│   ├── async
│   ├── autosuspend_delay_ms
│   ├── control
│   ├── runtime_active_kids
│   ├── runtime_active_time
│   ├── runtime_enabled
│   ├── runtime_status
│   ├── runtime_suspended_time
│   └── runtime_usage
├── subsystem -> ../../../../../class/uio
├── uevent
└── version

5 directories, 18 files
root@driver-virtual-machine ~/d/test# cat /sys/class/uio/uio0/name 
test_uio
root@driver-virtual-machine ~/d/test# cat /sys/class/uio/uio0/version 
1.0
root@driver-virtual-machine ~/d/test# cat /sys/class/uio/uio0/maps/map0/addr
0xffff9c2f5380c000
root@driver-virtual-machine ~/d/test# cat /sys/class/uio/uio0/maps/map0/name 
area1
root@driver-virtual-machine ~/d/test# cat /sys/class/uio/uio0/maps/map0/size
0x0000000000001000

相关函数与结构体

/* defines for uio_info->irq */
#define UIO_IRQ_CUSTOM	-1
#define UIO_IRQ_NONE	0

/* defines for uio_mem->memtype */
#define UIO_MEM_NONE	0
#define UIO_MEM_PHYS	1
#define UIO_MEM_LOGICAL	2
#define UIO_MEM_VIRTUAL 3
#define UIO_MEM_IOVA	4

/* defines for uio_port->porttype */
#define UIO_PORT_NONE	0
#define UIO_PORT_X86	1
#define UIO_PORT_GPIO	2
#define UIO_PORT_OTHER	3

/**
 * struct uio_mem - description of a UIO memory region
 * @name:		name of the memory region for identification
 * @addr:               address of the device's memory rounded to page
 *			size (phys_addr is used since addr can be
 *			logical, virtual, or physical & phys_addr_t
 *			should always be large enough to handle any of
 *			the address types)
 * @offs:               offset of device memory within the page
 * @size:		size of IO (multiple of page size)
 * @memtype:		type of memory addr points to
 * @internal_addr:	ioremap-ped version of addr, for driver internal use
 * @map:		for use by the UIO core only.
 */
struct uio_mem {
	const char		*name;
	phys_addr_t		addr;
	unsigned long		offs;
	resource_size_t		size;
	int			memtype;
	void __iomem		*internal_addr;
	struct uio_map		*map;
};

/**
 * struct uio_info - UIO device capabilities
 * @uio_dev:		the UIO device this info belongs to
 * @name:		device name
 * @version:		device driver version
 * @mem:		list of mappable memory regions, size==0 for end of list
 * @port:		list of port regions, size==0 for end of list
 * @irq:		interrupt number or UIO_IRQ_CUSTOM
 * @irq_flags:		flags for request_irq()
 * @priv:		optional private data
 * @handler:		the device's irq handler
 * @mmap:		mmap operation for this uio device
 * @open:		open operation for this uio device
 * @release:		release operation for this uio device
 * @irqcontrol:		disable/enable irqs when 0/1 is written to /dev/uioX
 */
struct uio_info {
	struct uio_device	*uio_dev;
	const char		*name;
	const char		*version;
	struct uio_mem		mem[MAX_UIO_MAPS];
	struct uio_port		port[MAX_UIO_PORT_REGIONS];
	long			irq;
	unsigned long		irq_flags;
	void			*priv;
	irqreturn_t (*handler)(int irq, struct uio_info *dev_info);
	int (*mmap)(struct uio_info *info, struct vm_area_struct *vma);
	int (*open)(struct uio_info *info, struct inode *inode);
	int (*release)(struct uio_info *info, struct inode *inode);
	int (*irqcontrol)(struct uio_info *info, s32 irq_on);
};

drivers-session3-uio-4public.pdf

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

linux UIO驱动实践 的相关文章

  • ioctl 命令的用户权限检查

    我正在实现 char 驱动程序 Linux 并且我的驱动程序中有某些 IOCTL 命令仅需要由 ADMIN 执行 我的问题是如何在 ioctl 命令实现下检查用户权限并限制非特权用户访问 IOCTL 您可以使用bool capable in
  • 执行命令而不将其保留在历史记录中[关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 在进行软件开发时 经常需要在命令行命令中包含机密信息 典型示例是将项目部署到服务器的凭据设置为环境变量 当我不想将某些命令存储在命令历史记
  • 适用于 Linux 的轻量级 IDE [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • linux下无法创建僵尸进程

    嗯 我有一个奇怪的问题 我无法在我的项目中创建僵尸进程 但我可以在其他文件中创建僵尸进程 有简单的说明 int main if fork 0 printf Some instructions n else sleep 10 wait 0 r
  • Google BQ:运行参数化查询,其中参数变量是 BQ 表目标

    我正在尝试从 Linux 命令行为 BQ 表目标运行 SQL 此 SQL 脚本将用于多个日期 客户端和 BQ 表目标 因此这需要在我的 BQ API 命令行调用中使用参数 标志 parameter 现在 我已经点击此链接来了解参数化查询 h
  • 在 .gitconfig 中隐藏 GitHub 令牌

    我想将所有点文件存储在 GitHub 上 包括 gitconfig 这需要我将 GitHub 令牌隐藏在 gitconfig 中 为此 我有一个 gitconfig hidden token 文件 这是我打算编辑并放在隐藏令牌的 git 下
  • 在centos中安装sqlite3 dev和其他包

    我正在尝试使用 cpanel 在 centos 机器上安装 sqlite dev 和其他库 以便能够编译应用程序 我对 debian 比 centos 更熟悉 我知道我需要的库是 libsqlite3 dev libkrb5 dev lib
  • 在 Linux 上以编程方式设置 DNS 名称服务器

    我希望能够通过我的 C C 程序为 Linux 上的 DNS 名称服务器添加 IP 地址 我在一个带有只读 etc resolv conf 的嵌入式平台上 这意味着我不能简单地将 nameserver xxx xxx xxx xxx 行添加
  • 执行“minikube start”命令时出现问题

    malik malik minikube start minikube v1 12 0 on Ubuntu 18 04 Using the docker driver based on existing profile Starting c
  • 如何阻止ubuntu在使用apt安装或更新软件包时弹出“Daemons using outdatedlibraries”? [关闭]

    Closed 这个问题是与编程或软件开发无关 help closed questions 目前不接受答案 我最近新安装了 Ubuntu 22 04 LTS 我发现每次使用 apt 安装或更新软件包时 它都会询问我有关Which servic
  • CMake 链接 glfw3 lib 错误

    我正在使用 CLion 并且正在使用 glfw3 库编写一个程序 http www glfw org docs latest http www glfw org docs latest 我安装并正确执行了库中的所有操作 我有 a 和 h 文
  • 使用 shell 脚本将行附加到 /etc/hosts 文件

    我有一个新的 Ubuntu 12 04 VPS 我正在尝试编写一个安装脚本来完成整个 LAMP 安装 我遇到问题的地方是在 etc hosts文件 我当前的主机文件如下所示 127 0 0 1 localhost Venus The fol
  • 使用包管理器时如何管理 Perl 模块?

    A 最近的问题 https stackoverflow com questions 397817 unable to find perl modules in intrepid ibex ubuntu这让我开始思考 在我尝试过的大多数 Li
  • 内核的panic()函数是否完全冻结所有其他进程?

    我想确认内核的panic 功能和其他类似kernel halt and machine halt 一旦触发 保证机器完全冻结 那么 所有的内核和用户进程都被冻结了吗 是panic 可以被调度程序中断吗 中断处理程序仍然可以执行吗 用例 如果
  • 与 pthread 的进程间互斥

    我想使用一个互斥体 它将用于同步对两个不同进程共享的内存中驻留的某些变量的访问 我怎样才能做到这一点 执行该操作的代码示例将非常感激 以下示例演示了 Pthread 进程间互斥体的创建 使用和销毁 将示例推广到多个进程作为读者的练习 inc
  • 在生产服务器上使用 Subversion 使文件生效的最佳方法是什么?

    目前我已经设置了 subversion 这样当我在 Eclipse PDT 中进行更改时 我可以提交更改 它们将保存在 home administrator 中项目文件 该文件具有 subversion 推荐的 branches tags
  • Linux 为一组进程保留一个处理器(动态)

    有没有办法将处理器排除在正常调度之外 也就是说 使用sched setaffinity我可以指示线程应该在哪个处理器上运行 但我正在寻找相反的情况 也就是说 我想从正常调度中排除给定的处理器 以便只有已明确调度的进程才能在那里运行 我还知道
  • 使用 gdb 调试 Linux 内核模块

    我想知道 API 在内核模块 中返回什么 从几种形式可以知道 这并不是那么简单 我们需要加载符号表来调试内核模块 所以我所做的就是 1 尝试找到内核模块的 text bss和 data段地址 2 在 gdb 中使用 add symbol f
  • 如何让 Node.js 作为后台进程运行并且永不死掉?

    我通过 putty SSH 连接到 linux 服务器 我尝试将其作为后台进程运行 如下所示 node server js 然而 2 5 小时后 终端变得不活动 进程终止 即使终端断开连接 我是否也可以使进程保持活动状态 Edit 1 事实
  • 使用自定义堆的类似 malloc 的函数

    如果我希望使用自定义预分配堆构造类似 malloc 的功能 那么 C 中最好的方法是什么 我的具体问题是 我有一个可映射 类似内存 的设备 已将其放入我的地址空间中 但我需要获得一种更灵活的方式来使用该内存来存储将随着时间的推移分配和释放的

随机推荐

  • php 获取今天开始的时间戳

    一天86400秒 time 61 time date 61 date 39 Y m d 39 time 今天的年月日 startTime 61 strtotime date 39 Y m d 39 time 今天开始时间的时间戳 endTi
  • Spring Boot Kafka概览、配置及优雅地实现发布订阅

    本文属于原创 xff0c 转载注明出处 xff0c 欢迎关注微信小程序小白AI博客 微信公众号小白AI或者网站 https xiaobaiai net 文章目录 1 前言2 Spring Kafka功能概览2 1 自动创建主题2 2 发送消
  • An Ota Package Tool

    文章目录 OtaPackageToolInstallationBinary InstallationInstalling Tool from Source UsagePreparationExamplesFull UpdatesIncrem
  • [开源]OTA打包工具

    文章目录 OTA打包工具 96 ota packer 96 安装二进制安装源码编译安装 使用准备示例全量包增量包生成关于OTA包版本之间文件变更类型说明 96 ota packer 96 使用条件License OTA打包工具 ota pa
  • [golang]包管理

    文章目录 1 GOPATH vs Go Modules2 Go Modules Go Module Proxy 和 goproxy cn3 Go Modules 相关知识3 1 语义化版本控制规范3 2 go mod3 3 go sum3
  • 【名名的Bazel笔记】自定义规则实现将多个静态库合并为一个动态库或静态库

    文章目录 1 前言2 自定义规则实现2 1 规则功能2 2 实现规则的理论基础2 3 规则代码实现 3 总结4 参考资料 1 前言 为了实现如标题所述的将多个静态库合并为一个动态库 xff0c 内置的 Bazel 规则是没有这个功能的 xf
  • 【名名的Bazel笔记】自定义工具链实现交叉编译

    文章目录 1 前言2 Non Platform 方式3 Platform 方式3 1 平台3 1 1 概述3 1 2 定义约束和平台3 1 3 通用的约束和平台3 1 4 指定平台构建 3 2 工具链3 3 Platform 43 Tool
  • PX4/Pixhawk---uORB深入理解和应用

    The Instructions of uORB PX4 Pixhawk 软件体系结构 uORB 主题发布 主题订阅 1 简介 1 1 PX4 Pixhawk的软件体系结构 PX4 Pixhawk的软件体系结构主要被分为四个层次 xff0c
  • join函数

    Python中我们经常会用到join函数 join函数的基本格式是 xff1a span class token string 39 39 span span class token punctuation span join span c
  • Glance详解

    Glance简介 Glance是OpenStack平台中负责镜像服务的组件 xff0c 其功能包括系统镜像的查找 注册和获取等 简单来说glance的功能就是用户可以通过其提供的REST API查询和获取镜像元数据 xff0c 通过Glan
  • 深入理解k8s中的service概念

    文章目录 service的概念kube proxy的作用kube proxy的三种模式Userspace Proxy ModeIptables Proxy ModeIPVS proxy mode service的概念 在k8s集群中 xff
  • Java_Save could not be completed. Try File> Save As. if the problem persists.

    所以最好不要用 开头的符号作为变量名 xff0c 变量名中含有一些奇怪的字符也会产生编码问题
  • Cinder详解

    文章目录 理解cindercinder架构cinder apicinder volumecinder schedulervolume providercinder DB cinder设计思想 理解cinder 操作系统得到存储空间一般有两种
  • shell:重启&&关机

    文章目录 shutdownhaltpoweroffrebootinitsync shutdown 关机重启命令 shutdown h 10十分钟后关机shutdown h 0马上关机shutdown h now马上关机shutdown c取
  • 基于docker的Jenkins-Gitlab-Ansible实现自动化部署

    环境准备 安装docker xff0c 略 拉取Jenkins Gitlab镜像 docker pull jenkins docker pull gitlab ce 部署 Jenkins 生成Jenkins span class token
  • SSH远程登录出现的常见问题与解决方法

    运维工程师经常会使用ssh远程登录主机 ssh的使用并不复杂 xff0c 但是也有可能会遇到各种各样的错误 xff0c 我在本篇博文中总结了一些常见的ssh报错信息与解决方法 Connection refused 可能原因 xff1a 网络
  • 从两个角度理解Kubernetes基本概念

    想要理解Kubernetes集群首先要思考两个问题 xff1a 它是由什么组成的 它是怎样工作的 而想要搞清楚这两个问题我们可以在两个不同的层面寻找答案 从物理层面看 从直观的层面来看 xff0c Kubernetes将多个物理机或虚拟机汇
  • 基于CentOS 7.6搭建Kubernetes 1.17.3集群demo

    本demo仅涉及基本的Kubernetes功能实践与介绍 xff0c 无高可用配置 部署版本为当前时间阿里开源镜像站提供的最新版本1 17 3 文章目录 部署环境安装准备域名解析关闭SELinux和防火墙配置yum源时间同步禁用swap加载
  • 为Kubernetes部署dashboard组件

    dashboard是Kubernetes社区中一个很受欢迎的开源项目 xff0c 它可以为使用者提供一个可视化web界面来进行Kubernetes的管理和使用 环境信息 组件版本Kubernetesv1 17 3dashboardv2 0
  • linux UIO驱动实践

    linux UIO驱动实践 环境搭建platform 设备驱动UIO驱动 环境搭建 Ubuntu20地址 虚拟机安装与配置见博客开头 xff1a 驱动虚拟环境搭建记录 一直以为用镜像直接安装的Ubuntu没有内核源码 xff0c 不能用来编