2015-11-25 15:44:05 goodtalent 阅读数 1370
  • C语言嵌入式Linux编程第7期:Linux内核常用的数据结构...

    学习嵌入式一段时间了,开始学习Linux内核、驱动了,发现看不懂?看内核代码还是感觉很吃力,云里雾里? 本期课程主要侧重于数据结构基本功的学习和Linux内核中的面向对象思想。掌握了这两项技能,再去分析Linux内核中复杂的子系统和驱动代码,相信你会跟以前有不一样的体验和收获。

    542 人正在学习 去看看 王利涛

今天有点时间,从一个具体的驱动着手,认识看了一下linux的内核基本知识:


1、Linux 下的 CONFIG_OF 选项,很多的项目中都有把这个宏打开。没有了解得很透彻,但是有了一个大体的印象。

Open Firmware. This was invented long time ago when Apple was producing laptops based on PowerPC CPUs. Openfirmware provides a good description of the devices connected to the platform. In Linux kernel the part that works with device data is called Device Tree (DT). More details in the Usage model.


2、late_initcall(battery_init)和module_init(battery_init);

late_initcall的优先级比module_init的低。


3、ret = alloc_chrdev_region(&adc_cali_devno, 0, 1, ADC_CALI_DEVNAME);

alloc_chrdev_region 动态分配设备编号。


4、CLOCK_MONOTONIC 字面意思是单调的时间,指的是自系统开机时间

      CLOCK_REALTIME真实的时间。

      HRTIMER_MODE_REL是相对时间

      标准hrtimer,使用api:

{      ktime_t ktime;

ktime = ktime_set(1, 0); /* 3s, 10* 1000 ms */
hrtimer_init(&battery_kthread_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
battery_kthread_timer.function = battery_kthread_hrtimer_func;
hrtimer_start(&battery_kthread_timer, ktime, HRTIMER_MODE_REL);

}



2013-03-01 15:31:02 shanzhizi 阅读数 4690
  • C语言嵌入式Linux编程第7期:Linux内核常用的数据结构...

    学习嵌入式一段时间了,开始学习Linux内核、驱动了,发现看不懂?看内核代码还是感觉很吃力,云里雾里? 本期课程主要侧重于数据结构基本功的学习和Linux内核中的面向对象思想。掌握了这两项技能,再去分析Linux内核中复杂的子系统和驱动代码,相信你会跟以前有不一样的体验和收获。

    542 人正在学习 去看看 王利涛
关于linux内核驱动的东西网络上有很多,但网上的东西还是感觉有点笼统,读过之后就忘了,还是需要写下来,或者写到本子上,自己形成一个概念好一些。读了这本书上的东西,把觉得好的东西写下来,已备不时之用,也强化记忆。
1 内核模块的概念
介绍内核模块的同时,也说明一下和应用程序的区别。虽然内容很多,但觉得都很有用。
1、内核模块是一些可以让操作系统内核在需要时载入和执行的代码,同时在不需要的时候可以卸载。这是一个好的功能,扩展了操作系统的内核功能,却不需要重新启动系统,是一种动态加载的技术。
特点:动态加载,随时载入,随时卸载,扩展功能
2、内核模块的加载作用:只是向linux内核预先注册自己,以便于将来的请求使用。
也就是告诉内核,它有了新增的功能,而并不马上使用(执行),而应用程序在加载后就开始执行。
3、内核模块的代码编写没有外部的函数库可以用,只能使用内核导出的函数。而应用程序习惯于使用外部的库函数,在编译时将程序与库函数链接在一起。例如对比printf( ) and printk( )
4、内核模块代码运行在内核空间,而应用程序在用户空间。应用程序的运行会形成新的进程,而内核模块一般不会。每当应用程序执行系统调用时,linux执行模式从用户空间切换到内核空间。
2linux内核模块的框架
最少两个入口点
*模块加载函数
*模块卸载函数
通常使用module_init() and module_exit()两个宏定义声明模块的加载函数和卸载函数,这个定义在linux2.6.x/include/linux/init.h中。内容为:
#define module_init(x) __initcall(x)
//在内核启动或模块加载时执行
#define module_exit(x) __exitcall(x)
//在模块卸载时执行
*每一个模块只能有一个module_init 和一个module_exit。
一个简单的内核模块的源代码.c格式如下:
-------------------------------------------------------------------------------------------
#include<linux/module.h>   //所有内核模块都必须包含这个头文件
#include<linux/kernel.h>    //使用内核信息优先级时要包含这个
#include<linux/init.h>         //一些初始化的函数如module_init()
MODULE_LICENSE("GPL");  //模块许可声明
static int hello_init(void)
{}
static void hello_exit(void)
{}
module_init(hello_init);
module_exit(hell0_exit);
这两个函数只是一种内部的声明,或者说内部实现加载的一种方式,说到低还是在模块内部的,内核并没有加载模块,真正要加载模块要使用命令insmod卸载用rmmod.
关于linux设备驱动入门请参考另外一篇文章:linux驱动编程入门
--------------------------------------------------------------------------------
3    2.6 系列内核模块的编译和加载
这里讲到内核模块的编译,2。6版本引入了kbuild,将外部内核模块的编译和内核源码树的编译统一起来了。
内核源码树的目录下都有两个文件Kconfig(2.4版本是Config.in)和Makefile。分布到各目录的Kconfig构成了一个分布式的内核配置数据库,每个Kconfig分别描述了所属目录源文件相关的内核配置菜单。在内核配置make menuconfig(或xconfig等)时,从Kconfig中读出菜单,用户选择后保存到.config的内核配置文件中。在内核编译时,主 Makefile调用这个.config,就知道了用户的选择。
*上面的内容说明了,Kconfig就是对应着内核的配置菜单。如果要想添加新的驱动到内核的源码中,可以修改Kconfig,这样就可以选择这个驱动,如果想使这个驱动被编译,要修改Makefile
添加新的驱动时需要修改的文件有两种(注意不只是两个)
*Kconfig
*Makefile
要想知道怎么修改这两种文件,就要知道两种文件的语法结构
3.1 Kconfig
每个菜单都有一个关键字标识,最常见的就是config
语法:
config
symbol是一个新的标记的菜单项,options是在这个新的菜单项下的属性和选项
其中options部分有:
1、类型定义:
每个config菜单项都要有类型定义,bool布尔类型、 tristate三态:内建、模块、移除 string字符串、 hex十六进制、 integer整型
例如config HELLO_MODULE
bool "hello test module"
bool类型的只能选中或不选中,tristate类型的菜单项多了编译成内核模块的选项,如果选择编译成内核模块,则会在.config中生成一个CONFIG_HELLO_MODULE=m的配置,如果选择内建,就是直接编译成内核影响,就会在.config中生成一个 CONFIG_HELLO_MODULE=y的配置.
2、依赖型定义depends on或requires
指此菜单的出现与否依赖于另一个定义
config HELLO_MODULE
bool "hello test module"
depends on ARCH_PXA
这个例子表明HELLO_MODULE这个菜单项只对XScale处理器有效。
3、帮助性定义
只是增加帮助用关键字help或者---help---
3.2 内核的Makefile


在linux2.6.x/Documentation/kbuild目录下有详细的介绍有关kernel makefile的知识。
linux2.6内核的Makefile分为5个组成部分:

Makefile     最顶层的Makefile 
.config        内核的当前配置文件,编译时成为定层Makefile的一部分
arch/$(ARCH)/Makefile    与体系结构相关的Makefile
s/ Makefile.*      一些Makefile的通用规则
kbuild Makefile           各级目录下的大概约500个文件,编译时根据上层Makefile传下来的宏定义和其他编译规则,将源代码编译成模块或者编入内核
顶层的Makefile文件读取 .config文件的内容,并总体上负责build内核和模块。Arch Makefile则提供补充体系结构相关的信息。 s目录下的Makefile文件包含了所有用来根据kbuild Makefile 构建内核所需的定义和规则。
(其中.config的内容是在make menuconfig的时候,通过Kconfig文件配置的结果,上面已经说过)
对于大部分内核模块或设备驱动的开发者和使用者来说,最常接触到的就是各层目录下基于kbuild架构的kbuild Makefile文件。主要部分有:
1、目标定义,目标定义就是用来定义哪些内容要做为模块编译,哪些要编译链接进内核。
最简单的只有一行,如
obj-y += foo.o
表示要由foo.c或者foo.s文件编译得到foo.o并链接进内核,而obj-m则表示该文件要作为模块编译。除了y,m以外的obj-x形式的目标都不会被编译。
由于既可以编译成模块,也可以编译进内核,更常见的做法是根据.config文件的CONFIG_ 变量来决定文件的编译方式,如:
obj-$(CONFIG_HELLO_MODULE) += hello.o ,这个已经在7.2.3.1中说明过。
除了obj-形式的目标以外,还有lib-y library库,hostprogs-y 主机程序等目标,但是基本都应用在特定的目录和场合下
2、多目标
一个内核模块由多个源文件编译而成,这是Makefile有所不同。
采用模块名加 –objs后缀或者 –y后缀的形式来定义模块的组成文件。
如以下例子:
obj-$(CONFIG_EXT2_FS) += ext2.o
ext2-y := balloc.o bitmap.o
ext2-$(CONFIG_EXT2_FS_XATTR) += xattr.o
模 块的名字为ext2,由balloc.o和bitmap.o两个目标文件最终链接生成ext2.o 直至ext2.ko文件,是否包括xattr.o取决于内核配置文件的配置情况。如果CONFIG_EXT2_FS的值是y也没有关系,在此过程中生成的 ext2.o将被链接进built-in.o最终链接进内核。这里需要注意的一点是,该kbuild Makefile所在的目录中不应该再包含和模块名相同的源文件如ext2.c/ext2.s
或者写成如-objs的形式:
obj-$(CONFIG_ISDN) += isdn.o
isdn-objs := isdn_net_lib.o isdn_v110.o isdn_common.o
3、目录的迭代
obj-$(CONFIG_EXT2_FS) += ext2/
如果CONFIG_EXT2_FS 的值为y或m,kbuild将会将ext2目录列入向下迭代的目标中,但是其作用也仅限于此,具体ext2目录下的文件是要作为模块编译还是链入内核,还是有ext2目录下的Makefile文件的内容来决定的
4、不同的模块编译方式
编译模块的时候,你可以将模块放在代码树中,用Make modules的方式来编译你的模块,你也可以将模块相关文件目录放在代码树以外的位置,用如下命令来编译模块:
make -C path/to/kernel/src M=$PWD modules
-C指定内核源码的根目录,$PWD 或 `PWD` 是当前目录的环境变量,告诉kbuild回到当前目录来执行build操作。
5、模块安装
当你需要将模块安装到非默认位置的时候,你可以用INSTALL_MOD_PATH 指定一个前缀,如:
make INSTALL_MOD_PATH=/foo modules_install
模块将被安装到 /foo/lib/modules目录下


注意2.6和2.4版本编译结果的不同,内核模块在2.6下是.ko后缀,取代了2.4下的.o后缀,使内核模块和普通的目标文件区别开。(比较好呀)
2014-09-07 17:28:30 aganlengzi 阅读数 978
  • C语言嵌入式Linux编程第7期:Linux内核常用的数据结构...

    学习嵌入式一段时间了,开始学习Linux内核、驱动了,发现看不懂?看内核代码还是感觉很吃力,云里雾里? 本期课程主要侧重于数据结构基本功的学习和Linux内核中的面向对象思想。掌握了这两项技能,再去分析Linux内核中复杂的子系统和驱动代码,相信你会跟以前有不一样的体验和收获。

    542 人正在学习 去看看 王利涛

GNOME

GNOME是一种让使用者容易操作和设定电脑环境的工具,GNOME 包含了 Panel (用来启动此程式和显示目前的状态)、桌面(应用程式和资料放置的地方)、及一系列的标准桌面工具和应用程式,并且能让各个应用程式都能正常地运作。不管之前使用何种操作系统,都能轻易地使用 GNOME 功能强大的图形接口工具。

KDE

KDE,K桌面环境(KoolDesktop Environment)的缩写。一种著名的运行于 Linux、Unix 以及FreeBSD 等操作系统上面自由图形工作环境,整个系统采用的都是 TrollTech 公司所开发的Qt程序库(现在属于Digia公司)。

KDE 和 Gnome 都是 Linux 操作系统上最流行的桌面环境系统

GRUB

GNU GRUBGRand Unified Bootloader简称“GRUB”)是一个来自GNU项目的多操作系统启动程序。GRUB是多启动规范的实现,它允许用户可以在计算机内同时拥有多个操作系统,并在计算机启动时选择希望运行的操作系统。GRUB可用于选择操作系统分区上的不同内核,也可用于向这些内核传递启动参数。

GNU

GNU计划,又称革奴计划,是由Richard Stallman1983927日公开发起的。它的目标是创建一套完全自由的操作系统。Richard Stallman最早是在net.unix-wizards新闻组上公布该消息,并附带《GNU宣言》等解释为何发起该计划的文章,其中一个理由就是要“重现当年软件界合作互助的团结精神”。为保证GNU软件可以自由地“使用、复制、修改和发布”,所有GNU软件都有一份在禁止其他人添加任何限制的情况下授权所有权利给任何人的协议条款,GNU通用公共许可证(GNU General Public License,GPL)。即“反版权”(或称Copyleft)概念。

BootLoader VS BIOS

Bootloader是嵌入式系统在加电后执行的第一段代码,在它完成CPU和相关硬件的初始化之后,再将操作系统映像或固化的嵌入式应用程序装在到内存中然后跳转到操作系统所在的空间,启动操作系统运行。
引导加载程序是系统加电后运行的第一段软件代码。

PC机中的引导加载程序由BIOS(其本质就是一段固件程序)和位于硬盘MBR中的OS BootLoader(比如,LILO和GRUB等)一起组成。
BIOS在完成硬件检测和资源分配后,将硬盘MBR中的BootLoader读到系统的RAM中,然后将控制权交给OS BootLoader。
BootLoader的主要运行任务就是将内核映象从硬盘上读到 RAM 中,然后跳转到内核的入口点去运行,也即开始启动操作系统。

例程

例程的作用类似于函数,但含义更为丰富一些。例程是某个系统对外提供的功能接口或服务的集合。比如操作系统的API、服务等就是例程;Delphi或C++Builder提供的标准函数和库函数等也是例程。我们编写一个DLL的时候,里面的输出函数就是这个DLL的例程。

可以这么简单地来理解:把一段相对独立的代码写成单独的一个模块就是函数的概念。我们可以在自己的程序中编写很多个函数,从而实现模块化编程。但这些模块或者说函数并不一定向外输出(即提供给别的程序使用),只用于当前这个程序里面。此时这些函数就仅仅具有独立函数的意义,但不是例程。

但如果我们把这些函数编写为DLL动态库的输出函数的话,此时虽然对于编写这个DLL的程序员来讲,仍然可以用函数的概念来理解这些DLL提供的功能,但对于以后调用这个DLL的程序来说,DLL里面提供的输出函数(或者说服务)就是例程了。因此“例程”的基本概念就包含了“例行事务性子程序”的含义,既然是例行的事务子程序,则必然通用性和相对独立性都比较强,所以很适合通过DLL、静态库(各种编程语言里面的库函数)、API、操作系统服务等方式来实现了。

管态与目态

大多数计算机系统将CPU执行状态分为目态与管态。
CPU的状态属于程序状态字PSW的一位。CPU交替执行操作系统程序和用户程序。
管态又叫特权态,系统态或核心态。CPU在管态下可以执行指令系统的全集。通常,操作系统在管态下运行。
目态又叫常态或用户态。机器处于目态时,程序只能执行非特权指令。用户程序只能在目态下运行,如果用户程序在目态下执行特权指令,硬件将发生中断,由操作系统获得控制,特权指令执行被禁止,这样可以防止用户程序有意或无意的破坏系统。
从目态转换为管态的唯一途径是中断。
从管态到目态可以通过修改程序状态字来实现,这将伴随这由操作系统程序到用户程序的转换。
PCI
PCI是Peripheral Component Interconnect(外设部件互连标准)的缩写,它是目前个人电脑中使用最为广泛的接口,几乎所有的主板产品上都带有这种插槽。PCI插槽也是主板带有最多数量的插槽类型,在目前流行的台式机主板上,ATX结构的主板一般带有5~6个PCI插槽,而小一点的MATX主板也都带有2~3个PCI插槽,可见其应用的广泛性。

ISA

ISA插槽是基于ISA总线(Industrial Standard Architecture,工业标准结构总线)的扩展插槽,其颜色一般为黑色,比PCI接口插槽要长些,位于主板的最下端。其工作频率为8MHz左右,为16位插槽,最大传输率16MB/sec,可插接显卡,声卡,网卡以及所谓的多功能接口卡等扩展插卡。其缺点是CPU资源占用太高,数据传输带宽太小,是已经被淘汰的插槽接口。


2011-05-31 23:51:00 yunsongice 阅读数 11533
  • C语言嵌入式Linux编程第7期:Linux内核常用的数据结构...

    学习嵌入式一段时间了,开始学习Linux内核、驱动了,发现看不懂?看内核代码还是感觉很吃力,云里雾里? 本期课程主要侧重于数据结构基本功的学习和Linux内核中的面向对象思想。掌握了这两项技能,再去分析Linux内核中复杂的子系统和驱动代码,相信你会跟以前有不一样的体验和收获。

    542 人正在学习 去看看 王利涛

距离2011年的6月不到半个小时了,从2009年的6月,我开始接触Linux以来,从安装第一个Linux到配置Linux网卡,从熟悉各种命令到使用标准glibc库进行编程,从学习PC汇编基础知识到能读懂Linux内核源代码,风风雨雨走过了整整两年。

 

两年前的我,还是个不知名小公司的PHP初级程序员;如今已是国内某著名公司的下一代云存储产品核心开发团队的一员。伴我走过的,是700多个csdn夜晚。每当我每看懂一个ULK章节,我就疯狂地把这个章节的内容记录到CSDN博客中,因为我害怕遗忘,害怕这些宝贵的知识悄悄溜走。于是,我就把这个疯狂的举动,命名为“疯狂内核”吧。同时,我也把这个疯狂的结果,分享给跟我同样热爱Linux、热爱开源世界的同仁们:

 

疯狂内核之——Linux预备知识
http://download.csdn.net/source/3326866

 

疯狂内核之——进程管理子系统
http://download.csdn.net/source/3326873

 

疯狂内核之——虚拟文件系统
http://download.csdn.net/source/3326879

 

疯狂内核之——虚拟内存子系统
http://download.csdn.net/source/3326886

 

疯狂内核之——内核初始化
http://download.csdn.net/source/3326894

 

Linux是一个伟大的操作系统,但它不是我工作的全部。一个好的IT系统,除了要运行在一个好的操作系统外,还必须有一个好的架构。能大致上分析Linux的核心代码,只是一个起点。在这个起点上,我必须迎接下一个挑战。接下来,我想把工作放在系统架构的研究上,重点研究设计模式和算法分析,特别是跟下一代云存储发展趋势密切相关的对象存储模式和DHT算法,希望在下一个两年后,能有新的成果跟大家一起分享。

2011-06-21 12:42:00 OtishionO 阅读数 7038
  • C语言嵌入式Linux编程第7期:Linux内核常用的数据结构...

    学习嵌入式一段时间了,开始学习Linux内核、驱动了,发现看不懂?看内核代码还是感觉很吃力,云里雾里? 本期课程主要侧重于数据结构基本功的学习和Linux内核中的面向对象思想。掌握了这两项技能,再去分析Linux内核中复杂的子系统和驱动代码,相信你会跟以前有不一样的体验和收获。

    542 人正在学习 去看看 王利涛

如果你对内核驱动模块一无所知,请先学习内核驱动模块的基础知识。

如果你已经入门了内核驱动模块,但是仍感觉有些模糊,不能从整体来了解一个内核驱动模块的结构,请赏读一下这篇拙文。

如果你已经从事内核模块编程N年,并且道行高深,也请不吝赐教一下文中的疏漏错误。

 

本文中我将实现一个简单的Linux字符设备,旨在大致勾勒出linux内核模块的编写方法的轮廓。其中重点介绍ioctl的用途。

我把这个简单的Linux字符设备模块命名为hello_mod.

设备类型名为hello_class

设备名为hello

 

该设备是一个虚拟设备,模块加载时会在/sys/class/中创建名为hello_class的逻辑设备,在/dev/中创建hello的物理设备文件。模块名为hello_mod,可接受输入字符串数据(长度小于128),处理该输入字符串之后可向外输出字符串。并且可以接受ioctl()函数控制内部处理字符串的方式。

例如:

a.通过write函数写入 “Tom”,通过ioctl函数设置langtype=chinese,通过read函数读出的数据将会是“你好!Tom/n”

b.通过write函数写入 “Tom”,通过ioctl函数设置langtype=english,通过read函数读出的数据将会是“hello!Tom/n”

c.通过write函数写入 “Tom”,通过ioctl函数设置langtype=pinyin,通过read函数读出的数据将会是“ni hao!Tom/n”

 

 

一般的内核模块中不会负责设备类别和节点的创建,我们在编译完之后会得到.o或者.ko文件,然后insmod之后需要mknod来创建相应文件,这个简单的例子中我们让驱动模块加载时负责自动创建设备类别和设备文件。这个功能有两个步骤,

1)创建设备类别文件  class_create();

2)创建设备文件   device_create();

关于这两个函数的使用方法请参阅其他资料。

 

linux设备驱动的编写相对windows编程来说更容易理解一点因为不需要处理IRP,应用层函数和内核函数的关联方式浅显易懂。

比如当应曾函数对我的设备调用了open()函数,而最终这个应用层函数会调用我的设备中的自定义open()函数,这个函数要怎么写呢,

我在我的设备中定义的函数名是hello_mod_open,注意函数名是可以随意定义,但是函数签名是要符合内核要求的,具体的定义是怎么样请看<linux/fs.h>

 

static int hello_mod_open(struct inode *, struct file *);

 

这样就定义了内核中的open函数,这只是定义还需要与我们自己的模块关联起来,这就要用到一个结构

 

struct file_operations

 

这个结构里面的成员是对应于设备操作的各种函数的指针。

我在设备中用到了这些函数所以就如下定义,注意下面的写法不是标准ANSI C的语法,而是GNU扩展语法。

 

struct file_operations hello_mod_fops =
{
 .owner = THIS_MODULE,
 .open = hello_mod_open,
 .read = hello_mod_read,
 .write = hello_mod_write,
 .ioctl = hello_mod_ioctl,
 .release = hello_mod_release,
};

 

这个结构体变量定义好之后我们在模块初始化函数中就可以通过register_chrdev()或者填充cdev结构来关联所有的操作到我们的模块函数了。

 

和设备交互的数据我们总称为“数据”,但是大致可划分为两种

“功能数据”:我们要输入设备处理的和设备处理完之后输出的数据。

“控制数据”:我们用来控制设备特性功能的命令和参数。

open,read,write,release等函数是对一个驱动模块的使用,就是我们对“设备的功能”的使用。但是一个设备有可能有很多功能,那么我们要怎么控制设备让设备完成指定的功能呢?

据个例子来说:假如我们有一个翻译机(姑且说机吧,也可能是器)实体设备,主要功能是输入中文,然后可以输出各种语言对应的翻译结果,那这个机的功能就是翻译,我们真正用来处理的数据是我们输入的中文,我们要得到的“设备功能”就是翻译后的输出内容,而各种语言则是我们的选择控制了,我们可设定这个设备翻译成何种语言。这就要求我们要向设备发送命令,设定目标语言。请注意我们要发送的是两个“控制数据”,命令和参数。

 

命令:一个设备可能有很多种行为,我们的命令就是代表我们要让设备执行何种行为。“复位”,“设定目标语言”,“获得当前目标语言”等

参数:对于某一个命令,可能需要参数可能不需要参数。

        比如:

        “复位”命令就不需要参数。

        “设定目标语言”则需要传入目标语言的类型。

        “获取目标语言”则需要传入一个能够接收目标语言类型的参数。

 

自己画了一个设备“数据流”图,希望能加深理解。

设备数据流图

 

对于我们自己的设备我们就要自己定义设备命令了,如果你要想搞清命令的格式,请参考其他资料关于ioctl的参数的介绍。

这里不打算介绍这个,只介绍ioctl实际功能。定义自己的IO控制命令需要用到宏。这里直接写出我的定义而不是介绍宏的实现

 

#define HELLO_MAGIC 12
#define HELLO_IOCTL_RESETLANG _IO(HELLO_MAGIC,0)  //设置复位,这个命令不带参数
#define HELLO_IOCTL_GETLANG  _IOR(HELLO_MAGIC,1,int) //获取当前设备的语言类型参数,参数是int型
#define HELLO_IOCTL_SETLANG  _IOW(HELLO_MAGIC,2,int) //设置设备的语言类型,参数是int型

 

多的不说了,下面贴上完整代码,懒人没写注释。。不好意思。

hello_mod.c

 

hello_mod_iotcl.h

 

Makefile

 

附上一用户层测试文件,编译后需要root身份执行。

 

编译和运行过程图

 

 

没有更多推荐了,返回首页