物理地址与虚拟地址关系
测试程序
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
static int a=0;
int main(int argc ,char **argv){
if(argc!=2){
perror("[user argv error]");
return -1;
}
//将传入的参数转换为long int型给a
char *endptr;
a=strtol(argv[1],&endptr,0);
if(a==ERANGE){
perror("[strtol error]");
return -1;
}
printf("a=%d\n",a);
printf("address for a=%lx\n",(unsigned long )&a);
printf("address for main=%lx\n",(unsigned long )main);
while (1){
sleep(100);
}
return 0;
}
测试
问题
当我们执行相同的程序时,它在内存中的实际地址与打印出的虚拟地址之间的关系是什么?
内存的映射机制和单片机的映射机制又有什么不同呢?
问题分析
单片机内存分析
对于单片机程序,我想大家一定很清楚
,因为在学习一块单片机的时候,我们总是能看到如下这副图片
这是157开发板的单板内存映射图
因此,从上面我们可以分析出一个对应关系,在单片机中,我们编写的单片机程序,通过被加载到flash/RAM中,CPU读取我们的程序以后,直接来控制相应设备的寄存器。因此,在单片机中我们的CPU发出的地址是准确的实际物理地址,工作流程如下
CPU发出一个地址,给内存控制器,内存控制器通过判断地址范围,来对硬件设备进行片选,当片选成功某一个硬件设备时,那么对于CPU而言,整个结构里面就只存在该设备一个设备。那么对于硬件的操作也就变成一对一处理的方式
因此,在单片机程序中,CPU发出的地址是一个实际的物理地址
。
Linux应用程序内存分析
操作系统诞生的原因,就在于我们要实现多任务。即不能像单片机程序那样,单一的处理某一个程序。因此在我们Linux应用程序中,CPU的工作模式如下
在运行操作系统的时候,会启动我们的MMU(内存管理单元),此时我们的CPU发出的地址不再是一个实际的物理地址,而是经过MMU转换的虚拟地址。
当我们的程序被加载进RAM时,MMU中会存储着该程序的实际内存地址与虚拟内存地址的映射表,当操作系统将程序的虚拟地址告诉CPU,CPU去读取时,将会发出该虚拟地址给MMU单元,MMU将虚拟地址进行转换,选中我们的RAM,进而使得CPU去处理我们的程序
。
因此,在Linux应用程序中,我们的CPU发出的是一个虚拟地址
,他会发送给MMU单元,通过MMU进行地址转换进而控制硬件。
这样做的好处是什么?
,这就使得,对于操作系统中的每一个程序而言,都能独享全部的内存空间。因为即时虚拟地址相同,也会在MMU的映射控制下,映射到不同的实际内存空间。这也使得内存的管理变得高效,安全起来。也因此诞生了内存的段页式存储
。
驱动程序的改进
问题
在上一篇博客中我们总是在挂载驱动之后要去对该驱动创建一个设备节点
sudo mknod /dev/xxx c xxx xxx
。
这是不是显得非常的麻烦,为什么就不能通过程序直接自动的创建设备节点呢?
答案是,完全可以。
解决方法
在驱动程序中,引入我们的 class_create函数和device_create函数
,以实现自动创建设备节点的功能
驱动程序
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
//设置主设备号
static int major=0;
//创建设备结构体对象
static struct class *class_for_hello;
//实现open函数
static int hello_open(struct inode *node, struct file *fp)
{
return 0;
}
//实现read函数
static ssize_t hello_read(struct file *fp, char __user *buf, size_t len, loff_t *pos)
{
int ret;
ret=copy_to_user(buf,"hello world",11);
return ret;
}
//实现write函数
static ssize_t hello_write(struct file *fp, const char __user *buf, size_t len, loff_t *pos)
{
return 0;
}
//定义文件操作结构体对象
static struct file_operations fops={
.owner=THIS_MODULE,
.open =hello_open,
.write=hello_write,
.read =hello_read,
};
//入口函数
static int __init hello_init(void)
{
printk("%s line %d init success \n",__FUNCTION__,__LINE__ );
major=register_chrdev(0,"hello_drv",&fops);
class_for_hello=class_create(THIS_MODULE,"hello_class"); //创建在/sys/class目录下
device_create(class_for_hello,NULL,MKDEV(major,0),NULL,"myhello");
return 0;
}
//出口函数
static void __exit hello_exit(void)
{
printk("%s line %d exit success \n",__FUNCTION__,__LINE__ );
//删除节点
device_destroy(class_for_hello,MKDEV(major,0));
//删除类对象
class_destroy(class_for_hello);
//注销设备号和驱动程序
unregister_chrdev(major,"hello_drv");
return ;
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("jacky");
测试应用程序
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define file "/dev/myhello"
static char buffer[20]={0};
int main(int argc ,char **argv){
int fd=open(file,O_RDWR);
if(fd<0){
perror("[open error]");
return -1;
}
printf("fd : %d\n",fd);
int ret=write(fd,"good",4);
if(ret<0){
perror("[write error]");
return -1;
}
printf("write : %d\n",ret);
ret=read(fd,buffer,sizeof(buffer)-1);
if(ret<0){
perror("[read error]");
return -1;
}
printf("read : %s\n",buffer);
return 0;
}
Makefile
ARCH := x86
CROSS_COMPILE :=
KERNEL_VERSION :=$(shell uname -r)
KERNEL_DIR :=/lib/modules/$(KERNEL_VERSION)/build
SOURCE := app_test.c
all:
make -C $(KERNEL_DIR) M=`pwd` modules
$(CROSS_COMPILE)gcc -o test $(SOURCE)
.PHONY:clean
clean:
make -C $(KERNEL_DIR) M=`pwd` modules clean
rm -rf modules.order build
obj-m +=hello_drv.o
测试
挂载驱动
查看 /proc/devices
查看/sys/class 目录
查看hello_class 目录内容
查看是否创建设备节点文件
执行测试程序