嵌入式内核及驱动开发初级_嵌入式驱动开发内核-程序员宅基地

技术标签: linux内核和驱动开发  驱动开发  

目录


第一部分

背景知识、内核实现

# 一、程序分类

# 二、计算机系统的层次结构

## 2.1 无操作系统的简单的两层结构

## 2.2 有操作系统的复杂的四层结构

# 三、 什么是操作系统

# 四、操作系统内核的实现模式

# 五、什么是设备驱动程序


第二部分

学习方法和环境搭建

# 一、为什么要学驱动开发

# 二、学习Linux驱动开发的前提条件

# 三、怎么学?


第三部分

内核模块添加新功能

1. 静态添加功能

# 一、向内核添加新功能

1. 新功能源码与Linux内核源码在同一目录结构下

2. 给新功能代码配置Kconfig(决定着menuconfig的图形显示那些个能代码模块)

3. 给新功能代码改写Makefile

4. make menuconfig  界面里将新功能对应的那项选择成<*>

5. make uImage

6. cp arch/arm/boot/uImage /tftpboot

7. 启动开发板观察串口终端中的打印信息

2. 动态添加内核模块功能

# 一、向内核添加新功能

## 1.1 动态加载法:

a、新功能源码与Linux内核源码在同一目录结构下时

b、新功能源码与Linux内核源码不在同一目录结构下时

c、主机ubuntu下使用ko文件

d、开发板Linux下使用ko文件


第四部分

内核模块的编译方法

# 一、内核模块基础代码解析

# 二、内核模块的多源文件编程

# 三、 内核模块信息宏


第五部分

内核模块传参和依赖

# 一、模块传参

# 二、模块依赖


第六部分

内核与用户空间

# 一、内核空间和用户空间

# 二、执行流

# 三、模块编程与应用编程的比较

# 四、内核接口头文件查询


第一部分


背景知识、内核实现



# 一、程序分类

程序按其运行环境分为:

1. 裸机程序:直接运行在对应硬件上的程序

2. 应用程序:只能运行在对应操作系统上的程序, 操作系统的生态指的就是各大app行业的开发人员愿不愿意开发对应操作系统的app。

# 二、计算机系统的层次结构

计算机系统两种层次结构:

## 2.1 无操作系统的简单的两层结构

## 2.2 有操作系统的复杂的四层结构

# 三、 什么是操作系统

狭义的操作系统:给应用程序提供运行环境的裸机程序,也被称为操作系统内核

广义的操作系统:一组软件集合,它包含:

1. 最核心的一个裸机程序  ----------内核 (kernel)

2. app开发常用的一些功能库(如:C语言标准函数库、线程库、C++标准类库、QT类库等等)

3. 一些管理用的特殊app(如桌面、命令  1行、app包管理器、资源管理器、系统设置、一些常用后台服务程序)

# 四、操作系统内核的实现模式

内核:操作系统最核心的那个裸机程序,主要负责硬件资源的驱动和管理。

一个操作系统内核主要包括如下几个子模块:

1. 任务管理:多任务支持、任务调度、任务间通讯, 任务就是进程嘛, 所以这模块就是进程管理模块。

2. 内存管理:物理内存管理,虚拟内存实现

3. 设备驱动:各种外部设备的I/O支持

4. 网络协议支持

5. 文件系统支持

6. 启动管理 是一个用来管理计算机启动项的工具或服务。它可以帮助用户控制在计算机启动时自动运行的程序和服务。

两种典型的内核实现模式:

1. 单内核(宏内核, 内核的体积比较大):所有子模块(子系统)代码编译到一个比较大的可执行文件(镜像文件)中,各子模块代码共用同一套运行资源,各模块间的交互直接通过函数调用来进行

2. 微内核:只将任务管理、内存管理、启动管理最基本的三个子模块编译到一个微型的可执行文件中,其它子模块则各自编译成独立的后台服务程序,这些服务程序与微型内核以及app间主要通过各种IPC进行通讯

单内核特点:效率高,稳定性低,扩展性差,安全性高,典型操作系统:UNIX系列、Linux

微内核特点:效率低,稳定性高,扩展性高,安全性低,典型操作系统:Windows,QNX

# 五、什么是设备驱动程序

英文:Device Driver

简称:驱动(Driver)

一种添加到操作系统中的特殊程序,主要作用是协助操作系统完成应用程序与对应硬件设备之间数据传送的功能

简言之,设备驱动程序就是操作系统中“驱动”对应硬件设备使之能正常工作的代码。

一个驱动程序主要完成如下工作:

1. 初始化设备,让设备做好开始工作的准备

2. 读数据:将设备产生的数据传递给上层应用程序

3. 写数据:将上层应用程序交付过来的数据传递给设备

4. 获取设备信息:协助上层应用程序获取设备的属性、状态信息(比如获取电脑屏幕的分辨率)

5. 设置设备信息:让上层应用程序可以决定设备的一些工作属性、模式(修改电脑屏幕的分辨率)

6. 其它相关操作:如休眠、唤醒、关闭设备等(为什么要关闭设备, 不用就关呗)

其中最核心的工作就是设备数据的输入和输出,因此计算机外部设备(外设)也被称为IO(和上层的应用APP配合)设备


知识补充:


  • 操作系统作用是给应用程序提供一个运行环境, 本质是一个运行在硬件之上的裸机程序, 给应用程序提供运行环境。
  • 操作系统的本质就是一份代码量很大的裸机程序。
  • .md文件是一种文本文件格式,使用Markdown语法编写。它是一种轻量级的标记语言,用于格式化文本,并且可以在多种平台上以可读性良好的方式展示文本内容。
  • 面向对象的编程中的库称为类库, 面向过程的库称为函数库。

第二部分


学习方法和环境搭建



# 一、为什么要学驱动开发

1. 理想原因:兴趣、成就感

2. 现实原因:高薪

# 二、学习Linux驱动开发的前提条件

1. 精通C语言

2. 能看懂硬件原理图

3. 阅读芯片手册不怵头

4. 有Linux操作系统的一些背景知识,比如说

   a. Linux常用命令的使用

   b. Linux常用系统调用函数的编程,尤其是IO相关函数

# 三、怎么学?

1. 学习任何方面编程的共同准则--------用代码来检验所学, 代码量来堆积。

2. 从简易入手,逐步深入,切忌太强的好奇心

3. 重点花在掌握原则、总结套路,知识点、相关函数不要死记硬背

4. 会搜善套, 使用搜索工具


知识补充:


1.二进制文件在cpu上运行具有平台属性。

2.设置文件为高权限

赋予一个文件可执行权限

3.为什么不在emmc中, 因为emmc有使用寿命, 擦写的次数有限, 影响使用寿命。所以就将需要经常擦写的东西放到SD卡里面。

4.linux内核启动之后运行的主进程

5.nfs和tftp的区别?

功能:NFS是一种高级的文件共享协议,它允许计算机之间通过网络访问、读取和写入文件,就像访问本地文件一样。TFTP则是一种简单的文件传输协议,它主要用于在局域网中传输文件,只支持文件的读取和写入。

总结来说,NFS适用于需要高级文件共享功能和安全性的场景,而TFTP适用于简单的文件传输需求。选择使用哪种协议取决于具体的应用场景和需求。

6. unzip tar -vxf

7.在windows下制作SD卡uboot原理说明

在Linux中,"dd"命令用于复制和转换文件或块设备。它可以用于创建镜像文件、备份数据、克隆磁盘、执行低级别数据恢复等任务。

8.secur crt           \\串口终端

第三部分


内核模块添加新功能



1. 静态添加功能

# 一、向内核添加新功能

## 1.1 静态加载法:

即新功能源码与内核其它代码一起编译进uImage文件内

1. 新功能源码与Linux内核源码在同一目录结构下

   在linux-3.14/driver/char/目录下编写myhello.c,文件内容如下:

 #include <linux/module.h>
 #include <linux/kernel.h>
   
   int __init myhello_init(void)
   {
   	printk("#####################################################\n");
   	printk("#####################################################\n");
   	printk("#####################################################\n");
   	printk("#####################################################\n");
          printk("myhello is running\n");
   	printk("#####################################################\n");
   	printk("#####################################################\n");
   	printk("#####################################################\n");
   	printk("#####################################################\n");
   	return 0;
   }
   
   void __exit myhello_exit(void)
   {
   	printk("myhello will exit\n");
   }
   MODULE_LICENSE("GPL");
   module_init(myhello_init);
   module_exit(myhello_exit);

2. 给新功能代码配置Kconfig(决定着menuconfig的图形显示那些个能代码模块)

 ```shell
   #进入myhello.c的同级目录
   cd  ~/fs4412/linux-3.14/drivers/char
   
   vim Kconfig
   #39行处添加如下内容:
   config MY_HELLO
   	tristate "This is a hello test"
   	help
   		This is a test for kernel new function
   ```

  

3. 给新功能代码改写Makefile

  

 ```shell

   #进入myhello.c的同级目录

   cd  ~/fs4412/linux-3.14/drivers/char

  

   vim Makefile

   #拷贝18行,粘贴在下一行,修改成:

   obj-$(CONFIG_MY_HELLO)     += myhello.o

   ```

  

4. make menuconfig  界面里将新功能对应的那项选择成<*>

   ```shell

   cd  ~/fs4412/linux-3.14

   make menuconfig

   #make menuconfig如果出错,一般是两个原因:

   #1. libncurses5-dev没安装

   #2. 命令行界面太小(太矮或太窄或字体太大了)

  

   ```

5. make uImage

6. cp arch/arm/boot/uImage /tftpboot

7. 启动开发板观察串口终端中的打印信息

2. 动态添加内核模块功能

# 一、向内核添加新功能

## 1.1 动态加载法:

即新功能源码与内核其它源码不一起编译,而是独立编译成内核的插件(被称为内核模块)文件.ko(内核模块文件)

a、新功能源码与Linux内核源码在同一目录结构下时

1. 给新功能代码配置Kconfig

2. 给新功能代码改写Makefile

3. make menuconfig  界面里将新功能对应的那项选择成<M>

4. make uImage

5. cp arch/arm/boot/uImage /tftpboot

6. make modules

   make modules会在新功能源码的同级目录下生成相应的同名.ko文件(生成的ko文件只适用于开发板linux)

   注意此命令执行前,开发板的内核源码已被编译

b、新功能源码与Linux内核源码不在同一目录结构下时

1. cd  ~/fs4412

2. mkdir mydrivercode

3. cd mydrivercode

4. cp  ../linux-3.14/drivers/char/myhello.c  .

5. vim Makefile

6. make    (生成的ko文件适用于主机ubuntu linux)

7. make   ARCH=arm           (生成的ko文件适用于开发板linux,注意此命令执行前,开发板的内核源码已被编译)

```shell

#file命令可以查看指定ko文件适用于哪种平台,用法:

file  ko文件

#结果带x86字样的适用于主机ubuntu linux,带arm字样的适用于开发板linux

```

c、主机ubuntu下使用ko文件

```shell

sudo insmod ./???.ko  #此处为内核模块文件名,将内核模块插入正在执行的内核中运行 ----- 相当于安装插件

lsmod #查看已被插入的内核模块有哪些,显示的是插入内核后的模块名

sudo rmmod ??? #,此处为插入内核后的模块名,此时将已被插入的内核模块从内核中移除 ----- 相当于卸载插件

sudo dmesg -C  #清除内核已打印的信息(之前的打印信息)

dmesg #查看内核的打印信息

```

d、开发板Linux下使用ko文件

```shell

#先将生成的ko文件拷贝到/opt/4412/rootfs目录下:

cp ????/???.ko  /opt/4412/rootfs

#在串口终端界面开发板Linux命令行下执行

insmod ./???.ko  #将内核模块插入正在执行的内核中运行 ----- 相当于安装插件

lsmod #查看已被插入的内核模块有哪些

rmmod ??? #将已被插入的内核模块从内核中移除 ----- 相当于卸载插件

内核随时打印信息,我们可以在串口终端界面随时看到打印信息,不需要dmesg命令查看打印信息

```


知识补充:


1.内核模块功能, <M>, 一般以 .ko为文件扩展名, 该内核模块类似浏览器的插件, 内核模块文件。

2.解压tgz格式的压缩包

3.以两个线程的方式编译内核源码

线程的个数和分配给cpu的核心数直接挂钩, 一般为虚拟机核心数的两倍。

4..ko文件为内核模块文件

5.ls快速查找

6.在menuconfig配置界面中增加新选项代码

修改Kconfig文件

第四部分


内核模块的编译方法



# 一、内核模块基础代码解析

Linux内核的插件机制——内核模块

类似于浏览器、eclipse这些软件的插件开发,Linux提供了一种可以向正在运行的内核中插入新的代码段、在代码段不需要继续运行时也可以从内核中移除的机制,这个可以被插入、移除的代码段被称为内核模块。

主要解决:

1. 单内核扩展性差的缺点(linux内核时单内核的, 可扩展性差)

2. 减小内核镜像文件体积,一定程度上节省内存资源

3. 提高开发效率

4. 不能彻底解决稳定性低的缺点:内核模块代码出错可能会导致整个系统崩溃

内核模块的本质:一段隶属于内核的“动态”代码,与其它内核代码是同一个运行实体,共用同一套运行资源,只是存在形式上是独立的。

微内核的内核设计模式是模块是独立的一个服务程序

#include <linux/module.h> //包含内核编程最常用的函数声明,如printk
#include <linux/kernel.h> //包含模块编程相关的宏定义,如:MODULE_LICENSE

/*该函数在模块被插入进内核时调用,主要作用为新功能做好预备工作
  被称为模块的入口函数
  __init的作用 : 
1. 一个宏,展开后为:__attribute__ ((__section__ (".init.text")))   实际是gcc的一个特殊链接标记
2. 指示链接器将该函数对应的指令放置在 .init.text区段
3. 在模块插入时方便内核从ko文件指定位置读取入口函数的指令到特定内存位置
4.作用,方便内核调用.ko文件里面的函数
*/
int __init myhello_init(void)
{
    /*内核是裸机程序,不可以调用C库中printf函数来打印程序信息,
    //广义的操作系统的组成:内核、 第三方库、 其它特殊用途的应用程序。

    Linux内核源码自身实现了一个用法与printf差不多的函数,命名为printk (k-kernel)
    printk不支持浮点数打印*/

	printk("#####################################################\n");
	printk("#####################################################\n");
	printk("#####################################################\n");
	printk("#####################################################\n");
	printk("myhello is running\n");
	printk("#####################################################\n");
	printk("#####################################################\n");
	printk("#####################################################\n");
	printk("#####################################################\n");
	return 0;
}

/*该函数在模块从内核中被移除时调用,主要作用做些init函数的反操作
  被称为模块的出口函数
  __exit的作用:
1.一个宏,展开后为:__attribute__ ((__section__ (".exit.text")))   实际也是gcc的一个特殊链接标记
2.指示链接器将该函数放置在 .exit.text区段
3.在模块插入时方便内核从ko文件指定位置读取出口函数的指令到另一个特定内存位置
*/
void __exit myhello_exit(void)
{
	printk("myhello will exit\n");
}



/*
MODULE_LICENSE(字符串常量);
字符串常量内容为源码的许可证协议 可以是"GPL" "GPL v2"  "GPL and additional rights"  "Dual BSD/GPL"  "Dual MIT/GPL" "Dual MPL/GPL"等, "GPL"最常用

其本质也是一个宏,宏体也是一个特殊链接标记,指示链接器在ko文件指定位置说明本模块源码遵循的许可证
在模块插入到内核时,内核会检查新模块的许可证是不是也遵循GPL协议,如果发现不遵循GPL,则在插入模块时打印抱怨信息:
	myhello:module license 'unspecified' taints kernel
	Disabling lock debugging due to kernel taint
也会导致新模块没法使用一些内核其它模块提供的高级功能
*/
MODULE_LICENSE("GPL");

/*
module_init 宏
1. 用法:module_init(模块入口函数名) 
2. 动态加载模块,对应函数被调用
3. 静态加载模块,内核启动过程中对应函数被调用
4. 对于静态加载的模块其本质是定义一个全局函数指针,并将其赋值为指定函数,链接时将地址放到特殊区段(.initcall段),方便系统初始化统一调用。
5. 对于动态加载的模块,由于内核模块的默认入口函数名是init_module,用该宏可以给对应模块入口函数起别名
*/
module_init(myhello_init);

/*
module_exit宏
1.用法:module_exit(模块出口函数名)
2.动态加载的模块在卸载时,对应函数被调用
3.静态加载的模块可以认为在系统退出时,对应函数被调用,实际上对应函数被忽略
4.对于静态加载的模块其本质是定义一个全局函数指针,并将其赋值为指定函数,链接时将地址放到特殊区段(.exitcall段),方便系统必要时统一调用,实际上该宏在静态加载时没有意义,因为静态编译的驱动无法卸载。
5.对于动态加载的模块,由于内核模块的默认出口函数名是cleanup_module,用该宏可以给对应模块出口函数起别名
*/
module_exit(myhello_exit);

模块三要素:入口函数 出口函数 MODULE__LICENSE

# 二、内核模块的多源文件编程

ifeq ($(KERNELRELEASE),)

ifeq ($(ARCH),arm)
KERNELDIR ?= 目标板linux内核源码顶层目录的绝对路径
ROOTFS ?= 目标板根文件系统顶层目录的绝对路径
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
endif
PWD := $(shell pwd)

modules:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

modules_install:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) INSTALL_MOD_PATH=$(ROOTFS) modules_install

clean:
	rm -rf  *.o  *.ko  .*.cmd  *.mod.*  modules.order  Module.symvers   .tmp_versions

else
obj-m += hello.o

endif
```

Makefile中:

obj-m用来指定模块名,注意模块名加.o而不是.ko

可以用 模块名-objs 变量来指定编译到ko中的所有.o文件名(每个同名的.c文件对应的.o目标文件)

一个目录下的Makefile可以编译多个模块:

添加:obj-m += 下一个模块名.o

# 三、 内核模块信息宏

MODULE_AUTHOR(字符串常量); //字符串常量内容为模块作者说明



MODULE_DESCRIPTION(字符串常量); //字符串常量内容为模块功能说明



MODULE_ALIAS(字符串常量); //字符串常量内容为模块别名

这些宏用来描述一些当前模块的信息,可选宏

这些宏的本质是定义static字符数组用于存放指定字符串内容,这些字符串内容链接时存放在.modinfo字段,可以用modinfo命令来查看这些模块信息,用法:

modinfo  模块文件名


知识补充:


  • "__attribute__ ((__section__ (".init.text")))是C编程语言中的一个指令,用于指定函数或变量应该放置在生成的二进制文件的特定部分。在这种情况下,__attribute__ ((__section__ (".init.text")))指定应将其应用于的函数或变量放置在二进制文件的.init.text部分。.init.text部分通常用于初始化代码或需要在调用主函数之前执行的功能
  • GPL内核源码的许可证协议
  • printk为linux内核源码自己实现的类似printf的函数
  • attribute的理解链接标记的作用是:在模块文件插入到内核中的时候内核去二进制文件中对应的区段中读出指令执行(不同的函数地址放在模块文件的不同区段中)由内核将二进制文件中复制到内存中区去运行。

第五部分


内核模块传参和依赖



# 一、模块传参

module_param(name,type,perm);//将指定的全局变量设置成模块参数

/*

name:全局变量名

type:

    使用符号      实际类型                传参方式

bool             bool           insmod xxx.ko  变量名=0 或 1

invbool      bool           insmod xxx.ko  变量名=0 或 1

charp        char *         insmod xxx.ko  变量名="字符串内容"

short        short          insmod xxx.ko  变量名=数值

int          int            insmod xxx.ko  变量名=数值

long         long           insmod xxx.ko  变量名=数值

ushort       unsigned short insmod xxx.ko  变量名=数值

uint         unsigned int   insmod xxx.ko  变量名=数值

ulong        unsigned long  insmod xxx.ko  变量名=数值

perm:给对应文件 /sys/module/name/parameters/变量名 指定操作权限

#define S_IRWXU 00700

#define S_IRUSR 00400

#define S_IWUSR 00200

#define S_IXUSR 00100

#define S_IRWXG 00070

#define S_IRGRP 00040

#define S_IWGRP 00020

#define S_IXGRP 00010

#define S_IRWXO 00007

#define S_IROTH 00004

#define S_IWOTH 00002  //不要用 编译出错

#define S_IXOTH 00001

*/

```





module_param_array(name,type,&num,perm);

/*

name、type、perm同module_param,type指数组中元素的类型

&num:存放数组大小变量的地址,可以填NULL(确保传参个数不越界)

    传参方式 insmod xxx.ko  数组名=元素值0,元素值1,...元素值num-1 

*/

可用MODULE_PARAM_DESC宏对每个参数进行作用描述,用法:

MODULE_PARM_DESC(变量名,字符串常量);`

字符串常量的内容用来描述对应参数的作用

modinfo可查看这些参数的描述信息

# 二、模块依赖

​    既然内核模块的代码与其它内核代码共用统一的运行环境,也就是说模块只是存在形式上独立,运行上其实和内核其它源码是一个整体,它们隶属于同一个程序,因此一个模块或内核其它部分源码应该可以使用另一个模块的一些全局特性。

一个模块中这些可以被其它地方使用的名称被称为导出符号,所有导出符号被填在同一个表中这个表被称为符号表。

最常用的可导出全局特性为全局变量和函数  

查看符号表的命令:nm

nm查看elf格式的可执行文件或目标文件中包含的符号表,用法:

`nm  文件名`  (可以通过man nm查看一些字母含义)

两个用于导出模块中符号名称的宏:

EXPORT_SYMBOL(函数名或全局变量名)

EXPORT_SYMBOL_GPL(函数名或全局变量名)   需要GPL许可证协议验证

使用导出符号的地方,需要对这些符号进行extern声明后才能使用这些符号

B模块使用了A模块导出的符号,此时称B模块依赖于A模块,则:

1. 编译次序:先编译模块A,再编译模块B,当两个模块源码在不同目录时,需要:i. 先编译导出符号的模块A ii. 拷贝A模块目录中的Module.symvers到B模块目录 iii. 编译使用符号的模块B。否则编译B模块时有符号未定义错误

2. 加载次序:先插入A模块,再插入B模块,否则B模块插入失败

3. 卸载次序:先卸载B模块,在卸载A模块,否则A模块卸载失败

补充说明:

内核符号表(直接当文本文件查看)

   /proc/kallsyms运行时    /boot/System.map编译后


知识补充:


T:函数

D、B:全局变量

左边的地址指的时相对于.ko可执行文件或者目标文件的相对地址。

  • 两个模块不用的目录下面, 先编译提供方, 将提供方生成的符号表, 这样使用方在编译的时候才不会出问题
  • 全局符号表的宏本质就是在编译生成可执行文件的时候把对应的使用该宏修饰的全局变量和函数对应.ko文件的对应区段中去, 这样别的.ko文件可以访问。
  • 先插入先编译提供方, 再者才是使用方。    移除先移除使用方, 再移除提供方
  • 使用nm命令查看elf格式的可执行文件或者查看目标文件的, 各个字符的含义
  • 模块传参之给数组传参

第六部分


内核与用户空间



# 一、内核空间和用户空间

为了彻底解决一个应用程序出错不影响系统和其它app的运行,操作系统给每个app一个独立的假想的地址空间,这个假想的地址空间被称为虚拟地址空间(也叫逻辑地址),操作系统也占用其中固定的一部分,32位Linux的虚拟地址空间大小为4G,并将其划分两部分:

1. 0~3G 用户空间 :每个应用程序只能使用自己的这份虚拟地址空间

  2. 3G~4G 内核空间:内核使用的虚拟地址空间,应用程序不能直接使用这份地址空间,但可以通过一些系统调用函数与其中的某些空间进行数据通信

实际内存操作时,需要将虚拟地址映射到实际内存的物理地址,然后才进行实际的内存读写

# 二、执行流

执行流:有开始有结束总体顺序执行的一段独立代码,又被称为代码上下文

计算机系统中的执行流的分类:

执行流:

1. 任务流--任务上下文(都参与CPU时间片轮转,都有任务五状态:就绪态  运行态  睡眠态  僵死态  暂停态)

   1.  进程

   2.  线程

       1.  内核线程:内核创建的线程

       2.  应用线程:应用进程创建的线程

2. 异常流--异常上下文

   1. 中断

   2. 其它异常

应用编程可能涉及到的执行流:

1. 进程

2. 线程    

内核编程可能涉及到的执行流: 

1. 应用程序自身代码运行在用户空间,处于用户态   -----------------  用户态app

2. 应用程序正在调用系统调用函数,运行在内核空间,处于内核态,即代码是内核代码但处于应用执行流(即属于一个应用进程或应用线程) ----  内核态app

3. 一直运行于内核空间,处于内核态,属于内核内的任务上下文 --------- 内核线程

4. 一直运行于内核空间,处于内核态,专门用来处理各种异常 --------- 异常上下文

# 三、模块编程与应用编程的比较

 不同点  

 内核模块                                                 

 应用程序                            

 API来源 

 不能使用任何库函数                          

 各种库函数均可以使用                

 运行空间

 内核空间                                           

 用户空间                            

 运行权限

 特权模式运行                                             

  非特权模式运行                      

 编译方式

 静态编译进内核镜像或编译特殊的ko文件        

 elf格式的应用程序可执行文件         

 运行方式

 模块中的函数在需要时被动调用                

 从main开始顺序执行                  

 入口函数

 init_module                                                          

 main                                

 退出方式

 cleanup_module                              

 main函数返回或调用exit              

 浮点支持

 一般不涉及浮点运算,因此printk不支持浮点数据

 支持浮点运算,printf可以打印浮点数据

 并发考虑

 需要考虑多种执行流并发的竞态情况            

 只需考虑多任务并行的竞态            

 程序出错

 可能会导致整个系统崩溃                      

 只会让自己崩溃                      

# 四、内核接口头文件查询

大部分API函数包含的头文件在include/linux目录下,因此:

1. 首先在include/linux 查询指定函数:grep  名称  ./   -r   -n

2. 找不到则更大范围的include目录下查询,命令同上


知识补充:


1.内存操作错误, 这里的内存的指的时虚拟内存, 对虚拟内存操作错误时内存管理单元将无法把虚拟地址映射到实际的物理地址上面去, 从而对应进程出错崩溃, 但是实际的物理内粗操作并没有发生错误, 所以步影响内核和其它进程的运行。

2。除了启动代码使用的是实际的物理内存地址之外, 在mmu(本质是一个在cpu和内存之间的一个硬件)运行起来之后接着的应用程序和内核模块驱动程序)使用的都是虚拟内存去跑程序的。

3.MMU的与cpu和内存的关系(虚拟地址给到cpu)

4.内核可以看成是一个特殊的进程, 进程号为0, linuxrc为主线进程, 进程号为1.

5.异常处理程序(异常上下文)不应该写的太复杂占用太多的时间, 不然会使cpu的时间片轮转机制卡很长时间, 给用户的体验会很差。

6.什么是内核空间什么是用户空间 他们之间怎么传递的?

内核空间和用户空间是操作系统中的两个重要概念,用来描述操作系统和应用程序的执行环境。

内核空间是操作系统的核心部分,它具有最高的特权级别,并可以直接访问硬件和系统资源。内核空间主要负责管理和控制整个系统的硬件资源、进程调度、内存管理、设备驱动程序等。内核空间通常是操作系统代码运行的地方,通常只有操作系统本身才能在内核空间执行。

用户空间是应用程序运行的地方,应用程序在用户空间中运行并执行各种任务。用户空间具有较低的特权级别,受到操作系统的保护,不能直接访问底层硬件和系统资源。应用程序通过系统调用来向内核发出请求,以便访问底层资源。系统调用提供了一个接口,使得应用程序可以向内核空间提交请求,并通过内核空间来完成特权操作,如文件读写、网络通信等。

内核空间和用户空间之间的数据传递通常通过系统调用实现。应用程序通过调用特定的系统调用函数,将数据传递给内核空间进行处理。内核会根据系统调用的类型解析请求,并在必要时进行相应的操作。一旦内核完成了请求的处理,它将结果返回给用户空间的应用程序。

在数据传递过程中,内核通过使用特定的数据结构(如缓冲区)来在内核空间和用户空间之间传递数据。应用程序将数据写入缓冲区并传递给内核,内核在接收到数据后可以对其进行处理,然后将结果再次写入缓冲区返回给应用程序。

总结起来,内核空间和用户空间是操作系统中不同的执行环境,通过系统调用和数据结构来实现数据传递和交互。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/m0_74976404/article/details/132509720

智能推荐

JWT(Json Web Token)实现无状态登录_无状态token登录-程序员宅基地

文章浏览阅读685次。1.1.什么是有状态?有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如tomcat中的session。例如登录:用户登录后,我们把登录者的信息保存在服务端session中,并且给用户一个cookie值,记录对应的session。然后下次请求,用户携带cookie值来,我们就能识别到对应session,从而找到用户的信息。缺点是什么?服务端保存大量数据,增加服务端压力 服务端保存用户状态,无法进行水平扩展 客户端请求依赖服务.._无状态token登录

SDUT OJ逆置正整数-程序员宅基地

文章浏览阅读293次。SDUT OnlineJudge#include<iostream>using namespace std;int main(){int a,b,c,d;cin>>a;b=a%10;c=a/10%10;d=a/100%10;int key[3];key[0]=b;key[1]=c;key[2]=d;for(int i = 0;i<3;i++){ if(key[i]!=0) { cout<<key[i.

年终奖盲区_年终奖盲区表-程序员宅基地

文章浏览阅读2.2k次。年终奖采用的平均每月的收入来评定缴税级数的,速算扣除数也按照月份计算出来,但是最终减去的也是一个月的速算扣除数。为什么这么做呢,这样的收的税更多啊,年终也是一个月的收入,凭什么减去12*速算扣除数了?这个霸道(不要脸)的说法,我们只能合理避免的这些跨级的区域了,那具体是那些区域呢?可以参考下面的表格:年终奖一列标红的一对便是盲区的上下线,发放年终奖的数额一定一定要避免这个区域,不然公司多花了钱..._年终奖盲区表

matlab 提取struct结构体中某个字段所有变量的值_matlab读取struct类型数据中的值-程序员宅基地

文章浏览阅读7.5k次,点赞5次,收藏19次。matlab结构体struct字段变量值提取_matlab读取struct类型数据中的值

Android fragment的用法_android reader fragment-程序员宅基地

文章浏览阅读4.8k次。1,什么情况下使用fragment通常用来作为一个activity的用户界面的一部分例如, 一个新闻应用可以在屏幕左侧使用一个fragment来展示一个文章的列表,然后在屏幕右侧使用另一个fragment来展示一篇文章 – 2个fragment并排显示在相同的一个activity中,并且每一个fragment拥有它自己的一套生命周期回调方法,并且处理它们自己的用户输_android reader fragment

FFT of waveIn audio signals-程序员宅基地

文章浏览阅读2.8k次。FFT of waveIn audio signalsBy Aqiruse An article on using the Fast Fourier Transform on audio signals. IntroductionThe Fast Fourier Transform (FFT) allows users to view the spectrum content of _fft of wavein audio signals

随便推点

Awesome Mac:收集的非常全面好用的Mac应用程序、软件以及工具_awesomemac-程序员宅基地

文章浏览阅读5.9k次。https://jaywcjlove.github.io/awesome-mac/ 这个仓库主要是收集非常好用的Mac应用程序、软件以及工具,主要面向开发者和设计师。有这个想法是因为我最近发了一篇较为火爆的涨粉儿微信公众号文章《工具武装的前端开发工程师》,于是建了这么一个仓库,持续更新作为补充,搜集更多好用的软件工具。请Star、Pull Request或者使劲搓它 issu_awesomemac

java前端技术---jquery基础详解_简介java中jquery技术-程序员宅基地

文章浏览阅读616次。一.jquery简介 jQuery是一个快速的,简洁的javaScript库,使用户能更方便地处理HTML documents、events、实现动画效果,并且方便地为网站提供AJAX交互 jQuery 的功能概括1、html 的元素选取2、html的元素操作3、html dom遍历和修改4、js特效和动画效果5、css操作6、html事件操作7、ajax_简介java中jquery技术

Ant Design Table换滚动条的样式_ant design ::-webkit-scrollbar-corner-程序员宅基地

文章浏览阅读1.6w次,点赞5次,收藏19次。我修改的是表格的固定列滚动而产生的滚动条引用Table的组件的css文件中加入下面的样式:.ant-table-body{ &amp;amp;::-webkit-scrollbar { height: 5px; } &amp;amp;::-webkit-scrollbar-thumb { border-radius: 5px; -webkit-box..._ant design ::-webkit-scrollbar-corner

javaWeb毕设分享 健身俱乐部会员管理系统【源码+论文】-程序员宅基地

文章浏览阅读269次。基于JSP的健身俱乐部会员管理系统项目分享:见文末!

论文开题报告怎么写?_开题报告研究难点-程序员宅基地

文章浏览阅读1.8k次,点赞2次,收藏15次。同学们,是不是又到了一年一度写开题报告的时候呀?是不是还在为不知道论文的开题报告怎么写而苦恼?Take it easy!我带着倾尽我所有开题报告写作经验总结出来的最强保姆级开题报告解说来啦,一定让你脱胎换骨,顺利拿下开题报告这个高塔,你确定还不赶快点赞收藏学起来吗?_开题报告研究难点

原生JS 与 VUE获取父级、子级、兄弟节点的方法 及一些DOM对象的获取_获取子节点的路径 vue-程序员宅基地

文章浏览阅读6k次,点赞4次,收藏17次。原生先获取对象var a = document.getElementById("dom");vue先添加ref <div class="" ref="divBox">获取对象let a = this.$refs.divBox获取父、子、兄弟节点方法var b = a.childNodes; 获取a的全部子节点 var c = a.parentNode; 获取a的父节点var d = a.nextSbiling; 获取a的下一个兄弟节点 var e = a.previ_获取子节点的路径 vue

推荐文章

热门文章

相关标签