1 内核空间和用户空间的概念
用户空间:0-3G
内核空间:3-4G
PAGE_OFFSET配置用户空间和内核空间的界限
分离的原因:
(1)处理器模式不同,权限不同
对于x86体系的cpu,用户空间代码运行在Ring3模式,内核空间代码运行Ring o模式;
对于arm体系的cpu,用户空间代码运行在usr模式,内核空间代码运行在svc模式;
(2)安全考量
整个系统中有各种资源,比如计算资源、内存资源和外设资源,而linux是多用户、多进程系统,所以,这些资源必须在受限的、被管理的状态下使用,要不然就陷入了混乱。至间隔离可以保证即便是单个应用程序出现错误也不会影响到操作系统的稳定性
(3)从软件设计思想来看,解除了核心代码和业务逻辑代码的耦合内核代码偏重于系统管理;而用户空间代码(也即应用程序)偏重于业务逻辑代码的实现。两者分工不同,隔离也是解耦。
用户空间的代码如何调用到内核空间中:
基于最早的字符设备代码修改: 《linux系统内核设计与实现》-实现最简单的字符设备驱动-CSDN博客
除了两个函数需要修改外,变量也需要修改
#define BUFFER_MAX (32) // buff大小
char buffer[BUFFER_MAX]; // 缓冲区
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/slab.h>
#define BUFFER_MAX (32) // buff大小
#define OK (0)
#define ERROR (-1)
struct cdev *gDev; // 字符设备结构体指针
struct file_operations *gFile; // 文件操作结构体指针
dev_t devNum; // 设备号
unsigned int subDevNum = 1; // 注册设备的数量
int reg_major = 232; // 主设备号
int reg_minor = 0; // 次设备号
char buffer[BUFFER_MAX]; // 缓冲区
/**
* printk 是内核中用于输出调试信息、错误信息和其他日志信息的函数。
* 它将消息输出到内核日志缓冲区,这些日志可以通过 dmesg 命令查看
* KERN_INFO 是一个宏:信息性消息
*/
// hello_open函数:打开文件
int hello_open(struct inode *p, struct file *f)
{
printk(KERN_INFO "hello_open\r\n");
return 0;
}
// hello_write函数
ssize_t hello_write(struct file *f, const char __user *u, size_t s, loff_t *l)
{
printk(KERN_INFO "hello_write\r\n");
int writelen = 0;
writelen = BUFFER_MAX > s ? s : BUFFER_MAX;
// 把用户空间数据拷贝到内核空间。把u拷到buffer,长度为writelen
// 返回值是拷贝失败的字节的个数
if (copy_from_user(buffer, u, writelen)) {
return -EFAULT;
}
return writelen;
}
// hello_read函数
ssize_t hello_read(struct file *f, char __user *u, size_t s, loff_t *l)
{
printk(KERN_INFO "hello_read\r\n");
int readlen;
readlen = BUFFER_MAX > s ? s : BUFFER_MAX;
// 把内核空间数据拷贝到用户空间。把u拷到buffer,长度为readlen
if (copy_to_user(u, buffer, readlen)) {
return -EFAULT;
}
return readlen;
}
int hello_init(void)
{
devNum = MKDEV(reg_major, reg_minor); // 根据主次设备号,生成devNum,唯一标识设备
// 把设备号注册到内核中。从devNum开始注册subDevNum个设备。
if (OK == register_chrdev_region(devNum, subDevNum, "helloworld")) {
printk(KERN_INFO "register_chrdev_region ok \n");
} else {
printk(KERN_INFO "register_chrdev_region error n");
return ERROR;
}
printk(KERN_INFO " hello driver init \n");
// 内核模块成功分配并初始化了一个字符设备结构
gDev = kzalloc(sizeof(struct cdev), GFP_KERNEL);
// 文件操作
gFile = kzalloc(sizeof(struct file_operations), GFP_KERNEL);
// 赋值。回调函数
gFile->open = hello_open;
gFile->read = hello_read;
gFile->write = hello_write;
gFile->owner = THIS_MODULE;
// 建立联系:通过这两行代码,驱动程序完成了字符设备的初始化和注册,使得字符设备可以被用户进程打开、读取和写入。
cdev_init(gDev, gFile); // 初始化字符设备结构体gDev,并将其与文件操作结构体gFile关联起来。
cdev_add(gDev, devNum, 1); // 将初始化好的字符设备gDev 添加到内核字符设备层,使其成为一个有效的字符设备,可以被用户空间访问和操作
return 0;
}
// 驱动退出函数
void __exit hello_exit(void)
{
printk(KERN_INFO " hello driver exit \n");
cdev_del(gDev); // 删除字符设备
kfree(gFile); // 释放内存
kfree(gDev); // 释放内存
unregister_chrdev_region(devNum, subDevNum); // 注销设备号
return;
}
module_init(hello_init); // 声明驱动的入口函数。执行insmod的时候调用
module_exit(hello_exit); // 声明驱动的退出函数。执行rmmod的时候调用
MODULE_LICENSE("GPL"); // 指定模块的许可证
记得先把原来的驱动卸载
dmesg -c # 清零内核日志
insmod helloDev.ko # 插入(加载)Linux内核模块的命令
dmesg # dmesg 命令用于查看和管理 Linux 内核的环形缓冲区中的消息
lsmod # 查看驱动
rmmod helloDev.ko # 卸载驱动
# 创建设备文件:名字 类型 【主设备号 次设备号】
mknod [OPTION]... NAME TYPE [MAJOR MINOR]
mknod /dev/hello c 232 0
write.c:将用户数据拷贝到内核空间
/*************************************************************************
> File Name: write.c
> Author: Winter
> Created Time: 2024年05月18日 星期六 18时58分19秒
************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/select.h>
#include <fcntl.h>
#define DATA_NUM (32)
int main(int argc, char* argv[])
{
int fd, i;
int w_len;
fd_set fdset;
char buf[DATA_NUM] = "hello world";
fd = open("/dev/hello", O_RDWR);
if (-1 == fd) {
perror("open file error\n");
return -1;
} else {
printf("open successe\n");
}
w_len = write(fd, buf, DATA_NUM);
if (w_len == -1) {
perror("write error\n");
return -1;
}
printf("write len: %d\n", w_len);
close(fd);
return 0;
}
read.c:把内核空间的数据拷贝到内核空间
/*************************************************************************
> File Name: read.c
> Author: Winter
> Created Time: 2024年05月18日 星期六 18时58分26秒
************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/select.h>
#include <fcntl.h>
#define DATA_NUM (32)
int main(int argc, char* argv[])
{
int fd, i;
int r_len;
fd_set fdset;
char buf[DATA_NUM];
memset(buf, 0, DATA_NUM);
fd = open("/dev/hello", O_RDWR);
if (-1 == fd) {
perror("open file error\n");
return -1;
} else {
printf ("open successe\n");
}
r_len = read(fd, buf, DATA_NUM);
if (r_len == -1) {
perror("write error\n");
return -1;
}
printf("read len: %d\n", r_len);
close(fd);
return 0;
}
先执行write,再执行read