一、内核的介绍
Linux内核是Linux操作系统的核心内容,它负责管理系统的硬件资源,并为上层的应用程序提供接口。(在上文都有所介绍)
功能:
- 进程管理:内核负责创建、调度、同步和终止进程。它还管理进程间的通信和数据传递,如管道、信号量和消息队列。
- 内存管理:内核负责分配和回收内存资源,确保每个进程都有足够的内存空间,同时防止一个进程访问另一个进程的内存空间。这包括内存分配、虚拟内存管理、内存保护等功能。
- 文件系统:Linux内核提供了多种文件系统的支持,如ext4、XFS、Btrfs、FAT32、NTFS等。它负责文件的创建、删除、读写、权限控制等操作。
- 设备驱动:内核包含了与硬件设备通信的驱动程序,这些驱动程序允许操作系统控制和管理各种硬件设备,如硬盘、显示器、键盘、鼠标等。
- 网络通信:内核实现了网络协议栈,支持TCP/IP和其他网络协议,使得Linux系统能够进行网络通信和数据传输。
- 安全性:Linux内核提供了多种安全机制,包括用户和组权限管理、访问控制列表(ACL)、安全模块(如SELinux)等,以保护系统安全。
- 虚拟化:内核支持虚拟化技术,如KVM(Kernel-based Virtual Machine),允许在单个物理机上运行多个虚拟机。
- 电源管理:内核负责管理电源使用,包括节能模式、休眠和唤醒等,以延长电池寿命和提高能效。
- 系统调用接口:内核提供了系统调用接口,允许用户空间的应用程序请求内核提供的服务,如打开文件、读取数据、执行系统命令等
二、Linux内核编译
1.1首先在官网中下载需要的源代库
The Linux Kernel Archives
本次使用的是2.6.32.2—mini2440
1.2解压下载好的文件(进入到文件所在的目录中进行解压)
sudo tar -xvf +文件名
1.3进入到文件目录
由于这里面的内容很多,文件也很多
所以需要使用makefile或者条件编译来编译
对于条件编译,通过#ifXX #endif来选择编译的对象
对于makefile
在.config文件中设置变量,控制makefile中的需要进行操作的文件或功能。
1.4配置内核(进入到可视化界面去)
配置文件千人千面,所以我们将一个官方的配置文件拷贝到.config中,在其中根据需要进行修改。
(1)首先在顶层目录中将默认配置拷贝到.condig中
cp config_mini2440_ td35.config 第一次编译内核
{
这个命令通常用于配置文件的替换或备份,特别是在嵌入式系统或Linux内核编译环境中,.config
文件经常用于存储内核配置选项。例如,如果你正在为基于ARM Cortex-A8的Mini2330开发板编译Linux内核,你可能会使用特定于该开发板的配置文件(如 config_mini2330_td35
)作为起点,并将其重命名为 .config
,以便内核编译系统能够识别和使用这些配置选项。
在执行这个命令之后,当前目录下的 config_mini2330_td35
文件将不再存在(因为它已经被复制并重命名为 .config
),而 .config
文件将包含原 config_mini2330_td35
文件的全部内容。如果 .config
文件已经存在,它将被新内容覆盖,除非你在 cp
命令中使用了额外的选项(如 -i
,它会在覆盖前询问用户
}
(2)在进入到可视化配置界面中
make menuconfig //可视化配置菜单——内核活地图
进入到该图形界面后,进行内核配置。配置时,大部分都是使用其默认选项,小部分才根据需要进行选择。
其中,每一个配置选项有三种选择,它们的含义如下:
<*>或[*]:表示将该功能编译进内核
[ ]:表示不将该功能编译进内核
[M]:表示将该功能编译成在需要时动态插入到内核的代码
用户根据需要进行配置。
(3)生成一个 U-Boot 兼容的映像文件(uImage)
make uImage
显示 ready则完成
如果出现
注释顶层目录中的 kernel/timeconst.pl 第373行
(4)将这个uImage文件拷贝到tftpboot中
(5)打开开发板sudo minicom 倒数之前按回车 进入到SMDK2410中
启动过程中 如果重新出现倒数界面
则
(6)验证
在
添加文件 如果出现在开发板那边即已经完成。
关于Image文件
二、在内核中添加新文件。
(1)在顶层目录下的/drivers/char/中新建一个文件夹kcf(名字自定义)
(2)进入到新建的目录中
(3)写入一个Kconfig
简单的写入
(4)这样写完之后也不能在menuconfig中显示新创的选项,
需要在char目录中的Kconfig中引用
(5)在menuconfig中可以找到相应的选项
(输入“/”则可开始寻找)
三、操作实例:
step1
step2 修改该目录下的makefile
step3:
在该目录下的Kconfig中加入配置选项
step4
回到/Linux-2.6.32.2 中 make uImage将
编译好的uImage在/Linux-2.6.32.2/arch/arm/boot中
将其复制到/home/linux/tftpboot中并用chmod 777 uImage给权限
在可视化界面中打开选项
再进到minicom中 tftp 0x30008000 uImage下载到开发板中
运行后可以显示:
再可视化目录中关闭选项后重复上述编译uImage的操作
####################会删除掉如下图
四、驱动介绍
在Linux内核开发中,驱动(Driver)是一种特殊的软件,它充当硬件设备和操作系统之间的接口。驱动程序允许操作系统控制和管理硬件设备,包括初始化设备、发送数据到设备、从设备接收数据以及检测和处理硬件错误等
顶层:应用程序(APP)
- APP open(led): 这表示一个用户空间的应用程序正在尝试打开一个名为“led”的设备。在Linux中,设备通常被当作文件来处理,因此使用
open
系统调用来访问它们。
系统调用层(SYS)
- SYS open(led) - inode: 当应用程序执行
open(led)
时,这个请求会通过系统调用接口(如syscall)传递给内核。内核中的sys_open
函数(或其他等效函数,具体取决于内核版本和架构)会处理这个请求。inode
是文件系统中的一个关键数据结构,用于存储文件的元数据(如权限、大小、创建时间等),但在这里它也被用来表示设备文件(如LED设备)的元数据。
内核层(Kernel)
- Kernel: 这是操作系统的核心部分,负责处理系统调用、管理硬件资源、提供进程调度等功能。
- Struct inode LED 1: 在内核中,与“led”设备相关的数据被存储在一个
inode
结构体中,这里特别标记为“LED 1”,可能表示这是第一个LED设备或某个特定编号的LED。这个结构体包含了设备所需的所有信息,以便内核能够与之交互。 - read() write() close(): 这些是内核中常见的文件操作函数,尽管对于LED这样的设备,
read
和write
操作可能不直接读取或写入数据(因为LED主要是一个输出设备),但它们可能被用来控制LED的状态(如打开、关闭、改变亮度等)。close
函数用于关闭设备文件,释放相关资源。
驱动程序层
- LED DRI, KEY_DRI, LCD_DRI: 这些是设备驱动程序的缩写,分别代表LED驱动、键盘驱动和LCD显示驱动。在Linux中,每个硬件设备都需要一个对应的驱动程序来与内核交互。这里的“DRI”可能指的是某种类型的驱动接口或规范,但更常见的是直接看到如“led_driver”这样的命名。
- 在这个示意图中,LED DRI是重点,它负责处理与LED设备相关的所有操作,如响应
open
、read
、write
和close
系统调用,以及直接与硬件通信以控制LED的状态。
底层:硬件
- LED, KEY, LCD: 这些是实际的硬件设备,分别代表LED灯、键盘和LCD显示屏。在这个上下文中,LED是重点,它根据驱动程序发出的指令来打开、关闭或改变其状态。
综上所述,这张图片展示了Linux系统中应用程序如何通过系统调用与内核交互,进而通过驱动程序控制硬件设备的基本流程。尽管它有所简化,但它很好地概括了Linux驱动程序的工作原理。
五、编写自己的驱动程序
1、我们需要把驱动程序放在
写的是字符数据驱动
Makefile和Kconfig文件改好 make uImage后生成下图所示才算完成
2、关于module_init(demo_init);和module_exit函数
module_init
宏
module_init
是一个特殊的宏,用于指定当模块被加载到内核时应该调用的初始化函数。它通常位于模块源文件的末尾,以确保在编译时能够正确地与内核模块机制相链接。module_init
宏接受一个函数指针作为参数,这个函数就是模块的初始化函数。
module_exit
宏
与 module_init
类似,module_exit
宏用于指定当模块从内核卸载时应该调用的清理函数。它也接受一个函数指针作为参数,这个函数负责执行必要的清理工作。
在内核源代码中,module_exit
宏的定义与 module_init
类似,但它是用于模块卸载的上下文中
3、测试函数能否成功
类似前面几项的操作
选中menu选项后
不选择选项后井号消失。
4、关于设备号
设备号有32位;
主设备号:设备类型
次设备号: 同类设备的编号
必须要手动创建设备节点
mknod /dev/demo c 255 0
/dev/demo 设备节点号
255 主设备号
0 次设备号
2、关于操作方法(opration)
有一个非常重要的结构体
strruct file_operation
{
'''''''''''
}
其中实际上是一系列的函数指针
ssize是文件偏移量。
头文件<linux/fs.h>
在Linux内核模块中定义一个简单的字符设备驱动,并为其实现了基本的文件操作函数:打开(
open
)、读取(read
)、写入(write
)和关闭(close
)。不过,需要注意的是,在Linux内核中,close
函数通常被命名为release
或release_file
,因为标准的文件操作结构体file_operations
中并没有直接命名为close
的成员。此外,printk
函数用于在内核日志中打印信息,这对于调试和跟踪内核模块的行为非常有用。int open (struct inode * inode, struct file * file) { printk("demo open ...\n"); return 0; } ssize_t read (struct file * file, char __user * buf, size_t len, loff_t * offset) { printk("demo read ...\n"); return 0; } ssize_t write (struct file * file, const char __user * buf, size_t len, loff_t * offset) { printk("demo write ...\n"); return 0; } int close (struct inode * inode, struct file * file) { printk("demo close ...\n"); return 0; } static struct file_operations fops = { .owner = THIS_MODULE, .open = open, .read = read, .write = write, .release = close //给方法赋值 };
3、关于绑定设备号跟操作方法
达到的效果:
需要使用一个结构体
将设备号码和操作方法都放在结构体中
结构体中包含链表的节点。
在代码中实现
4.向内核注册驱动节点。
需要使用到注册函数
register_chrdev_region(dev,1,DEV_NAME)
(需要在上面加一个
#define DEV_NAME "demo")
注册代码的具体伪代码
static struct cdev cdev;
static dev_t dev;
static int __init demo_init(void)
{
dev = MKDEV(MAJOR_NUM, MINOR_NUM);
cdev_init(&cdev, &fops);
cdev_add(&cdev, dev, DEV_NUM);
register_chrdev_region(dev, DEV_NUM, DEV_NAME);
printk("demo_init ###############################\n");
return 0;
}
5、卸载函数做与init()逆操作即可
static void __exit demo_exit(void)
{
unregister_chrdev_region(dev, DEV_NUM);
cdev_del(&cdev);
printk("demo_exit ###############################\n");
}
6,需要写一个应用程序
在/home/linux/nfs/rootfs中编写APP程序
运行:
只这样做开发板方面还是还是会找不到文件。
是因为设备中还没有设备节点
在arm端运行后需要手动添加节点
总体代码:
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/cdev.h>
#define MAJOR_NUM 255
#define MINOR_NUM 0
#define DEV_NAME "demo"
static int open(struct inode * node, struct file * file)
{
printk("demo open ...\n");
return 0;
}
static ssize_t read(struct file * file, char __user * buf, size_t len, loff_t * loff)
{
printk("demo read ...\n");
return 0;
}
static ssize_t write(struct file * file, const char __user * buf, size_t len, loff_t * loff)
{
printk("demo write ...\n");
return 0;
}
static int close(struct inode * node, struct file * file)
{
printk("demo close ...\n");
return 0;
}
static dev_t dev_num;
static struct cdev dev;
static struct file_operations fops =
{
.owner = THIS_MODULE,
.open = open,
.read = read,
.write = write,
.release = close
};
static int __init demo_init(void)
{
int ret = 0;
dev_num = MKDEV(MAJOR_NUM, MINOR_NUM); ;//(MAJOR_NUM << 20) | MINOR_NUM;
ret = cdev_add(&dev, dev_num, 1);
if(ret < 0)
goto err1;
cdev_init(&dev, &fops);
ret = register_chrdev_region(dev_num, 1, DEV_NAME);
if(ret < 0)
goto err2;
printk("demo_init ###############################\n");
return 0;
err1:
cdev_del(&dev);
printk("demo cdev_add failed ret = %d\n", ret);
return ret;
err2:
unregister_chrdev_region(dev_num, 1);
cdev_del(&dev);
printk("demo register_chrdev_region failed ret = %d\n", ret);
return ret;
}
static void __exit demo_exit(void)
{
unregister_chrdev_region(dev_num, 1);
cdev_del(&dev);
printk("demo_exit ###############################\n");
}
module_init(demo_init);
module_exit(demo_exit);