1、内核空间和用户空间的概念简介

在Linux中,操作系统和驱动程序运行在内核空间,应用程序运行在用户空间
两者不能简单的使用指针传递数据,因为Linux使用的虚拟内核机制,当内核空间使用用户空间指针时,对应的数据可能不在内存中(数据已被换出)。用户空间的内存采用段页式,内核空间也有自己的规则。

32位的Linux内核虚拟地址空间,一般低3G的地址(0-3G)划分为用户空间,高1G的地址(3~4G)划为内核空间,也就是说内核空间可以使用的线性地址只有1G。

另外也可以自行配置内核空间和用户空间的界限(大小),感兴趣自行搜索学习。
在这里插入图片描述
为什么要这样划分呢?

• 处理器模式不同,权限不同
对于X86体系的cpu,用户空间代码运行在Ring3横式,内核空间代码运行Ring0模式;
对于arm体系的cpu,用户空间代码运行在usr横式,内核空间代码运行在svc模式;
• 安全考虑
整个系统中有各种资源,比如计箅资源、内存资源和外设资源,而linux是多用户、多进程系统,所以,这些资源必须在受限的、被管理的状态下使用,要不然就陷入了混乱,空间隔离可以保证即便是单个应用程序出现错误也不会影响到操作系统的稳定性
• 从软件设计思想来看,解除了核心代码和业务逻辑代码的耦合
内核代码偏重于系统管理;而用户空间代码(也即应用程序)偏重于业务逻辑代码的实现,
两者分工不同,隔离也是解耦合

2、内核空间和用户空间的数据传递

在这里插入图片描述
用户空间的应用程序,在业务实现时,
1、如果很简单,比如你只实现个printf("hello world");,并没有和内核进行交互,那么就不会存在两个空间的数据拷贝。
2、但是实际上,很多的业务实现都要与内核进行交互,各种上网,操作一些字符、块设备,调用open()、write()、read()、ioctl()等等函数,就要用到系统调用。这些都会触发一个中断,并通过中断的方式陷入到内核去执行,但用到的还是用户态进程,然后将进程需要的数据返回到用户空间。
更加细节的可自行再去学习。

下面我们就用代码来展示下内核空间和用户空间之间的数据拷贝。
helloworld.c

#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 (64)
#define OK (0)
#define ERROR (-1)

struct cdev *gDev;
struct file_operations *gFile;

dev_t devNum;

unsigned int subDevNum = 1;
int reg_major = 813;
int reg_minor = 0;
char *buffer;

int hello_open(struct inode *p, struct file *f)
{
	 printk(KERN_INFO "hello_open\r\n");
	 return 0;
}
ssize_t hello_write(struct file *f, const char __user *u, size_t s, loff_t *l)//*f为file的结构体指针,__user为编译器汇编扩展,用来说明*u为用户空间的地址,s是长度,*l为返回的offset
{
	 printk(KERN_INFO "hello_write\r\n");
	 int writelen = 0;
	 writelen = BUFFER_MAX>s ? s : BUFFER_MAX;
	 if(copy_from_user(buffer,u,writelen)){
		 return EFAULT;
	 }
	 return writelen;
}
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 = 0;
	 readlen = BUFFER_MAX > s ? s : BUFFER_MAX;
	 if(copy_to_user(u,buffer,readlen)){
		 return EFAULT;
	 }	
	 return readlen;
}
int hello_init(void)
{
	 devNum = MKDEV(reg_major, reg_minor);
	 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);
	   
	 cdev_add(gDev, devNum, 1);
		
	 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);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

在加载完成内核驱动后,下面就是写用户空间的程序并调用这个驱动了。
废话不多说直接上代码:
write.c

#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/select.h>

#define DATA_NUM 64

int main(int argc,char *argv[]){
	int fd,i;
	int r_len,w_len;
	fd_set fdset;
	char buf[DATA_NUM] = "hello world";
	memset(buf,0,sizeof(buf));
	fd = open("/dev/hello",O_RDWR);
	//printf("%d\n",fd);

	if(-1 == fd){
		perror("open file error\n");
		return -1;
	}
	else{
		printf("open successe\n");
	}
	w_len = write(fd,buf,DATA_NUM);
	if(-1 == w_len){
		perror("write_error\n");
		return -1;
	}
	printf("write_length = %d \n",w_len);
	close(fd);
	return 0;
}

read.c

#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/select.h>

#define DATA_NUM 64

int main(int argc,char *argv[]){
	int fd,i;
	int r_len,w_len;
	fd_set fdset;
	char buf[DATA_NUM];
	memset(buf,0,sizeof(buf));
	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(-1 == r_len){
		perror("read_error");
		return -1;
	}
	printf("read_length = %d\n", r_len);
	printf("%s\n",buf);
	close(fd);
	return 0;
}

解释下:write.c是将用户态的字符数组buf[DATA_NUM],写入到内核空间。之后使用read.c将内核空间的字符数组读出来。

这里自行编译执行下吧。

参考:

https://blog.csdn.net/tommy_wxie/article/details/17122923/

Logo

更多推荐