一、业务需求和分析
业务需求:实现LED灯的开和关(主板上的灯)
业务分析:需要的资源有两种,分别是:描述硬件的资源(寄存器地址资源或者中断资源)和其他用于设备标识的资源(例如,设备名称,设备文件名称,操作寄存器的值等等)
二、编程实现步骤
准备工作:
1、查看主板原理图,分析LED所在的GPIO口,为GPIOZ(以下操作都以GPIOZ5为例)
2、准备GPIOZ的资源和RCC的资源
起始地址:GPIOZ的起始地址,并且定义在寄存器的头文件中
结束地址:GPIOZ的起始地址 + 寄存器结构体大小 - 1(每个GPIO都有自己的寄存器结构体)
开始编程:
device.c中:
1、寄存器资源------可以用struct resource来进行传递
这里需要两个寄存器资源:一个是RCC寄存器的资源,用于打开时钟,
一个是GPIO寄存器的资源
我们用结构体描述
struct resource led_resource[]={
[0]={ //GPIOZ的资源
.start =GPIOZ_Base, //GPIOZ寄存器的起始地址
.end =GPIOZ_Base+sizeof(GPIO_TypeDef)-1,//GPIOZ寄存器的结束地址
.name ="GPIOZ", //资源的名字
.flags =IORESOURCE_MEM, //寄存器地址资源
},
[1]={ //RCC的资源
.start =RCC_Base,
.end =RCC_Base+sizeof(RCC_TypeDef)-1,
.name ="RCC",
.flags =IORESOURCE_MEM,
},
};
2、用于设备标识的资源------可以用void *platform_data; 来传递
这是在定义自定的资源,因为这些资源会随着硬件的变化而改变
我们可以将这些易改变的资源全部定义在一个头文件中(platform_data_special.h)
当我们需要的时候,可以定义一个结构体(Platform_data_spex)来调用需要的资源
(自定义资源)看第五点
platform_data_special.h头文件
#ifndef __PLATFORM_DATA_SPECIAL__
#define __PLATFORM_DATA_SPECIAL__
struct Platform_data_spex{
char *name; // 设备名
unsigned int minum; // 次设备号
unsigned int MODER_SET;
unsigned int MODER_RESET;
unsigned int OTYPER_SET;
unsigned int OTYPER_RESET;
unsigned int OSPEEDR_SET;
unsigned int OSPEEDR_RESET;
unsigned int PUPDR_SET;
unsigned int PUPDR_RESET;
unsigned int IDR_SET;
unsigned int IDR_RESET;
unsigned int ODR_SET;
unsigned int ODR_RESET;
unsigned int BSRRL_SET;
unsigned int BSRRL_RESET;
unsigned int BSRRH_SET;
unsigned int BSRRH_RESET;
unsigned int LCKR_SET;
unsigned int LCKR_RESET;
unsigned int BRR_SET;
unsigned int BRR_RESET;
unsigned int SECCFGR_SET;
unsigned int SECCFGR_RESET;
unsigned int RCC_MP_REGISTER; //操作寄存器的哪一个位
unsigned int RCC_OFFSET; //在基地址的基础上偏移多少
};
#endif //__PLATFORM_DATA_SPECIAL__
struct Platform_data_spex led_special={
.name ="drv_led", //这用于描述设备文件名,也用于描述这个设备
.minum =0, //设备次设备号
.MODER_SET = 1<<10, //查看手册了解以下的参数
.MODER_RESET = 3<<10,
.ODR_SET = 1<<5,
.ODR_RESET = 1<<5,
.RCC_MP_REGISTER = 0,
.RCC_OFFSET = 0x210/4,
};
3、在描述设备的结构体中增加用于设备标识的数据地址
struct platform_device pdev={
.name = "stm32mp157a_led",
.id = -1,
.dev = {
.release =drv_led_release,
.platform_data=&led_special, //这是新加的,其余的没有改变,别乱来
},
.num_resources = ARRAY_SIZE(led_resource),
.resource = led_resource,
};
4,添加头文件
#include "linux/platform_device.h"
#include "stm32mp157xxx.h"
#include "platform_data_special.h"
driver.c中:
1、实例化对象,申请设备号,创建设备节点,映射寄存器地址资源--probe函数中实现
struct stm32mp157axxx{
unsigned int major;
struct class *cls;
struct device *devi;
struct resource *led_resource[2];
struct Platform_data_spex *led_pdata;
//这里需要定义成unsigned int型,因为传递RCC参数时,我们算的是从起始地址偏移多少个字节,所以不能用RCC_TypeDef
unsigned int *rcc;
GPIO_TypeDef *gpio;
};
struct stm32mp157axxx *drv_led;
2、这里实现fops结构体的三个函数功能
open-close-write
1,硬件初始化---------------open函数中
int drv_led_open(struct inode *inode, struct file *filp)
{
int ret=0;
printk("-------------%s-----------------\n",__FUNCTION__);
if(!(*(drv_led->rcc+drv_led->led_pdata->RCC_OFFSET)&(1<<drv_led->led_pdata->RCC_MP_REGISTER))){ //判断时钟是否是默认开启
*(drv_led->rcc+drv_led->led_pdata->RCC_OFFSET) |=(1<<drv_led->led_pdata->RCC_MP_REGISTER); //如果时钟未开始,打开时钟
}
// 设置GPIOZ的第5个引脚的模式
drv_led->gpio->MODER &= ~(drv_led->led_pdata->MODER_RESET);
drv_led->gpio->MODER |= drv_led->led_pdata->MODER_SET;
// 默认关灯
drv_led->gpio->ODR &= ~(drv_led->led_pdata->ODR_RESET);
return ret;
}
2,实现write函数
// 实现write接口
ssize_t drv_led_write(struct file *filp, const char __user *buf, size_t size, loff_t *flag)
{
int ret = 0;
int on;
printk("-------------%s-----------------\n",__FUNCTION__);
ret = copy_from_user(&on,buf,size);
if(ret < 0){
printk("drv_led_write: copy_from_user is error\n");
return ret;
}
if(1 == on){
drv_led->gpio->ODR |= (drv_led->led_pdata->ODR_SET);
}else{
drv_led->gpio->ODR &= ~(drv_led->led_pdata->ODR_RESET);
}
return ret;
}
fops结构体
const struct file_operations fops={
.open =drv_led_open,
.release=drv_led_close,
.write =drv_led_write,
};
3、编写probe函数
// 实例化对象
drv_led =kzalloc(sizeof(struct stm32mp157a_led), GFP_KERNEL);
if(IS_ERR(drv_led)){
printk("probe:drv_led kzalloc is error\n");
ret=PTR_ERR(drv_led);
return ret;
}
// 申请设备号
drv_led->major=register_chrdev(0,led_special->name,&fops);
if(drv_led->major<0){
ret=drv_led->major;
printk("probe:drv_led register_chrdev is error\n");
goto kzalloc_err;
}
// 创建类
drv_led->cls=class_create(THIS_MODULE, led_special->name);
if(IS_ERR(drv_led->cls)){
printk("probe:drv_led class_create is error\n");
ret=PTR_ERR(drv_led->cls);
goto register_chrdev_err;
}
// 创建设备节点
drv_led->dev=device_create(drv_led->cls,NULL,MKDEV(drv_led->major,led_special- >minum),NULL,led_special->name);
if(IS_ERR(drv_led->dev)){
printk("probe:drv_led device_create is error\n");
ret=PTR_ERR(drv_led->dev);
goto class_create_err;
}
// 映射寄存器地址
drv_led->GPIO=ioremap(led_reg_resource[0]->start,led_reg_resource[0]->end - led_reg_resource[0]->start + 1);
if(IS_ERR(drv_led->GPIO)){
printk("probe:drv_led(GPIO) ioremap is error\n");
ret=PTR_ERR(drv_led->GPIO);
goto device_create_err;
}
drv_led->RCC=ioremap(led_reg_resource[1]->start,led_reg_resource[1]->end - led_reg_resource[1]->start + 1);
if(IS_ERR(drv_led->RCC)){
printk("probe:drv_led(RCC) ioremap is error\n");
ret=PTR_ERR(drv_led->RCC);
goto ioremap_gpio_err;
}
return ret;
ioremap_gpio_err:
iounmap(drv_led->GPIO);
device_create_err:
device_destroy(drv_led->cls,MKDEV(drv_led->major,led_special->minum));
class_create_err:
class_destroy(drv_led->cls);
register_chrdev_err:
unregister_chrdev(drv_led->major,led_special->name);
kzalloc_err:
kfree(drv_led);
return ret;
4、编写remove函数
int drv_led_remove(struct platform_device *pdev)
{
int ret=0;
printk("-------------%s-----------------\n",__FUNCTION__);
iounmap(drv_led->gpio);
iounmap(drv_led->rcc);
//删除设备文件
device_destroy(drv_led->cls,MKDEV(drv_led->major, drv_led->led_pdata->minum));
class_destroy(drv_led->cls);//删除类
unregister_chrdev(drv_led->major,drv_led->led_pdata->name); //删除设备号
kfree(drv_led);
return ret;
}
5、 匹配
//这个名字必须和device.c中的platform_device中的name名字一致
const struct platform_device_id led_id_table[]={
{"stm32mp157xxx_led"},
};
6、定义 pdev结构体
struct platform_driver pdrv={ //具体的驱动
.probe =drv_led_probe, //在驱动driver.c和device.c匹配成功后,会调用这个函数
.remove =drv_led_remove, //在移除driver.c和device.c中的任何一个会调用的函数
.driver ={
.name ="drv_led", //描述这个驱动的名字
},
.id_table =led_id_table, //用于和硬件设备匹配的名字数组
};
7、入口函数-出口函数-声明
8、添加头文件
#include "linux/slab.h"
#include "linux/fs.h"
#include "linux/device.h"
#include "linux/io.h"
三、完整程序
app.c文件
#include "stdio.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
int main(void)
{
int fd;
int on;
fd = open("/dev/drv_led0",O_RDWR);
if(fd < 0){
perror("open");
exit(1);
}
while(1){
printf("please input a data:");
scanf("%d",&on);
write(fd,&on,sizeof(on));
}
close(fd);
return 0;
}
Makefile文件
Kernel_Dir =/home/xyn/fs_mp157/kernel/linux-stm32mp-5.4.31-r0/linux-5.4.31
Cue_Dir =$(shell pwd)
Myapp =app_test
all:
make -C $(Kernel_Dir) M=$(Cue_Dir) modules
$(CC) $(Myapp).c -o $(Myapp)
clean:
make -C $(Kernel_Dir) M=$(Cue_Dir) clean
rm $(Myapp)
install:
cp ./*.ko $(Myapp) /opt/rootfs/drv_module/
obj-m =platform_led_driver.o
obj-m +=platform_led_device.o
special(自定义资源头文件)
#ifndef __PLATFORM_DATA_SPECIAL__
#define __PLATFORM_DATA_SPECIAL__
struct Platform_data_spex{
char *name; // 设备名
unsigned int minum;// 次设备号
unsigned int MODER_SET;
unsigned int MODER_RESET;
unsigned int OTYPER_SET;
unsigned int OTYPER_RESET;
unsigned int OSPEEDR_SET;
unsigned int OSPEEDR_RESET;
unsigned int PUPDR_SET;
unsigned int PUPDR_RESET;
unsigned int IDR_SET;
unsigned int IDR_RESET;
unsigned int ODR_SET;
unsigned int ODR_RESET;
unsigned int BSRRL_SET;
unsigned int BSRRL_RESET;
unsigned int BSRRH_SET;
unsigned int BSRRH_RESET;
unsigned int LCKR_SET;
unsigned int LCKR_RESET;
unsigned int BRR_SET;
unsigned int BRR_RESET;
unsigned int SECCFGR_SET;
unsigned int SECCFGR_RESET;
unsigned int RCC_MP_REGISTER; //操作寄存器的哪一个位
unsigned int RCC_OFFSET; //在基地址的基础上偏移多少
};
#endif //__PLATFORM_DATA_SPECIAL__
device.c
// 1,头文件
#include "linux/init.h"
#include "linux/module.h"
#include "linux/platform_device.h"
#include "stm32mp157xxx.h"
#include "platform_data_special.h"
//这是在定义自定的资源,因为这些资源会随着硬件的变化而改变
struct Platform_data_spex special_data={
.name ="drv_led0",
.minum =0,
.MODER_SET =0x1<<10,
.MODER_RESET =0x3<<10,
.ODR_SET =0x1<<5,
.ODR_RESET =0x1<<5,
.RCC_MP_REGISTER=0,
.RCC_OFFSET = 0x210/4,
};
struct resource led_resource[]={//具体的地址资源或者中断资源
[0]={//RCC的地址资源
.start =RCC_Base,
.end =RCC_Base+sizeof(RCC_TypeDef)-1,
.name ="rcc",
.flags =IORESOURCE_MEM,
},
[1]={//GPIO的地址资源
.start =GPIOZ_Base,
.end =GPIOZ_Base+sizeof(GPIO_TypeDef)-1,
.name ="gpioz",
.flags =IORESOURCE_MEM,
},
};
void drv_led_release(struct device *dev)
{
//必须实现,可以不干事,在卸载这个驱动时,会被调用,如果找不到这个函数,系统会提示错误
}
struct platform_device pdev={//描述这个硬件设备的信息
.name ="stm32mp157xxx_led",//匹配的名字,这个硬件设备和谁去匹配
.id =-1,//一般为-1
.dev ={//自定义资源在这个里面定义,暂时我还没用
.release =drv_led_release,//在卸载时会报错,所以需要实现
.platform_data =&special_data,//将定义好的资源传给driver.c
},
.num_resources =sizeof(led_resource)/sizeof(led_resource[0]),//有多少个struct resource这个类型的结构体数据,用总的数据长度/一个数据的长度
.resource =led_resource,//要传递的资源的首地址
};
// 2,入口函数----在加载,insmod的时候调用的函数
static int __init dev_led_init(void)
{
printk("-------------%s-----------------\n",__FUNCTION__);
return platform_device_register(&pdev);//在平台总线中注册一个硬件设备
}
// 3,出口函数---在卸载这个模块,rmmod的时候调用
static void __exit dev_led_exit (void)
{
printk("-------------%s-----------------\n",__FUNCTION__);
platform_device_unregister(&pdev);//在平台总线中移除一个硬件设备
}
// 声明
module_init(dev_led_init);//声明入口函数是哪一个函数
module_exit(dev_led_exit);//声明哪一个函数是出口函数
MODULE_LICENSE("GPL");
driver.c
// 1,头文件
#include "linux/init.h"
#include "linux/module.h"
#include "linux/platform_device.h"
#include "linux/mod_devicetable.h"
#include "linux/slab.h"
#include "platform_data_special.h"
#include "linux/fs.h"
#include "linux/device.h"
#include "asm/io.h"
#include "stm32mp157xxx.h"
#include "linux/uaccess.h"
struct stm32mp157axxx{
unsigned int major;
struct class *cls;
struct device *devi;
struct resource *led_resource[2];
struct Platform_data_spex *led_pdata;
unsigned int *rcc;
GPIO_TypeDef *gpio;
};
struct stm32mp157axxx *drv_led;
int drv_led_open(struct inode *inode, struct file *filp)
{
int ret=0;
printk("-------------%s-----------------\n",__FUNCTION__);
if(!(*(drv_led->rcc+drv_led->led_pdata->RCC_OFFSET)&(1<<drv_led->led_pdata->RCC_MP_REGISTER))){ //判断时钟是否是默认开启
*(drv_led->rcc+drv_led->led_pdata->RCC_OFFSET) |=(1<<drv_led->led_pdata->RCC_MP_REGISTER);//如果时钟未开始,打开时钟
}
// 设置GPIOZ的第5个引脚的模式
drv_led->gpio->MODER &=~(drv_led->led_pdata->MODER_RESET);
drv_led->gpio->MODER |=drv_led->led_pdata->MODER_SET;
// 默认关灯
drv_led->gpio->ODR &=~(drv_led->led_pdata->ODR_RESET);
return ret;
}
int drv_led_close(struct inode *inode, struct file *filp)
{
int ret=0;
printk("-------------%s-----------------\n",__FUNCTION__);
return ret;
}
// 实现write接口
ssize_t drv_led_write(struct file *filp, const char __user *buf, size_t size, loff_t *flag)
{
int ret=0;
int on;
printk("-------------%s-----------------\n",__FUNCTION__);
ret=copy_from_user(&on,buf,size);
if(ret<0){
printk("drv_led_write: copy_from_user is error\n");
return ret;
}
if(1==on){
drv_led->gpio->ODR |=(drv_led->led_pdata->ODR_SET);
}else{
drv_led->gpio->ODR &=~(drv_led->led_pdata->ODR_RESET);
}
return ret;
}
const struct file_operations fops={
.open = drv_led_open,
.release = drv_led_close,
.write = drv_led_write,
};
int drv_led_probe(struct platform_device *pdev)
{
int ret=0;
printk("-------------%s-----------------\n",__FUNCTION__);
drv_led=kzalloc(sizeof(struct stm32mp157axxx),GFP_KERNEL);
if(IS_ERR(drv_led)){
ret=PTR_ERR(drv_led);
printk("drv_led_probe: drv_led kzalloc is error\n");
return ret;
}
drv_led->led_resource[0]=platform_get_resource(pdev,IORESOURCE_MEM,0);
printk("led_resource[0]->start=0x%x,led_resource[0]->end=0x%x\n",drv_led->led_resource[0]->start,drv_led->led_resource[0]->end);
drv_led->led_resource[1]=platform_get_resource(pdev,IORESOURCE_MEM,1);
printk("led_resource[1]->start=0x%x,led_resource[1]->end=0x%x\n",drv_led->led_resource[1]->start,drv_led->led_resource[1]->end);
drv_led->led_pdata =pdev->dev.platform_data;
printk("drv_led->led_pdata->name=%s\n",drv_led->led_pdata->name);
printk("drv_led->led_pdata->minum=%d\n",drv_led->led_pdata->minum);
printk("drv_led->led_pdata->MODER_SET=%d\n",drv_led->led_pdata->MODER_SET);
printk("drv_led->led_pdata->MODER_RESET=%d\n",drv_led->led_pdata->MODER_RESET);
printk("drv_led->led_pdata->ODR_SET=%d\n",drv_led->led_pdata->ODR_SET);
printk("drv_led->led_pdata->ODR_RESET=%d\n",drv_led->led_pdata->ODR_RESET);
printk("drv_led->led_pdata->RCC_MP_REGISTER=%d\n",drv_led->led_pdata->RCC_MP_REGISTER);
printk("drv_led->led_pdata->RCC_OFFSET=%d\n",drv_led->led_pdata->RCC_OFFSET);
//1,申请设备号-----动态分配
drv_led->major=register_chrdev(0,drv_led->led_pdata->name,&fops);
if(drv_led->major<0){
printk("drv_led_init: drv_led register_chrdev is error\n");
ret =drv_led->major;
goto register_chrdev_err;
}
// 2,创建设备文件
// 1,创建类
drv_led->cls = class_create(THIS_MODULE,drv_led->led_pdata->name);
//判断drv_led->cls 创建类是否成功
if(IS_ERR(drv_led->cls)){//判断drv_led->cls这个类创建是否成功,如果指向空,IS_ERR(drv_led->cls)的结果就为真
printk("drv_led_init: drv_led class_create is error\n");
ret=PTR_ERR(drv_led->cls);//获得错误码
goto class_create_err;
}
// 2,创建设备文件
drv_led->devi=device_create(drv_led->cls,NULL,MKDEV(drv_led->major, drv_led->led_pdata->minum),NULL,drv_led->led_pdata->name);
//判断drv_led->devi 创建类是否成功
if(IS_ERR(drv_led->devi)){//判断drv_led->devi这个类创建是否成功,如果指向空,IS_ERR(drv_led->devi)的结果就为真
printk("drv_led_init: drv_led device_create is error\n");
ret=PTR_ERR(drv_led->devi);//获得错误码
goto device_create_err;
}
drv_led->rcc=ioremap(drv_led->led_resource[0]->start,drv_led->led_resource[0]->end-drv_led->led_resource[0]->start+1);
if(IS_ERR(drv_led->rcc)){
printk("drv_led_init: drv_led->rcc ioremap is error\n");
ret=PTR_ERR(drv_led->rcc);//获得错误码
goto rcc_ioremap_err;
}
drv_led->gpio=ioremap(drv_led->led_resource[1]->start,drv_led->led_resource[1]->end-drv_led->led_resource[1]->start+1);
if(IS_ERR(drv_led->gpio)){
printk("drv_led_init: drv_led->gpio ioremap is error\n");
ret=PTR_ERR(drv_led->gpio);//获得错误码
goto gpio_ioremap_err;
}
return ret;
gpio_ioremap_err:
iounmap(drv_led->rcc);
rcc_ioremap_err:
device_destroy(drv_led->cls,MKDEV(drv_led->major, drv_led->led_pdata->minum));//删除设备文件
device_create_err:
class_destroy(drv_led->cls);//删除类
class_create_err:
unregister_chrdev(drv_led->major,drv_led->led_pdata->name);//删除设备号
register_chrdev_err:
kfree(drv_led);//当申请设备号失败,释放指针空间
return ret;
}
int drv_led_remove(struct platform_device *pdev)
{
int ret=0;
printk("-------------%s-----------------\n",__FUNCTION__);
iounmap(drv_led->gpio);
iounmap(drv_led->rcc);
device_destroy(drv_led->cls,MKDEV(drv_led->major, drv_led->led_pdata->minum));//删除设备文件
class_destroy(drv_led->cls);//删除类
unregister_chrdev(drv_led->major,drv_led->led_pdata->name);//删除设备号
kfree(drv_led);
return ret;
}
const struct platform_device_id led_id_table[]={
{"stm32mp157xxx_led"},//这个名字必须和device.c中的platform_device中的name名字一致
};
struct platform_driver pdrv={ //具体的驱动
.probe =drv_led_probe, //在驱动driver.c和device.c匹配成功后,会调用这个函数
.remove =drv_led_remove, //在移除driver.c和device.c中的任何一个会调用的函数
.driver ={
.name ="drv_led", //描述这个驱动的名字
},
.id_table =led_id_table, //用于和硬件设备匹配的名字数组
};
// 2,入口函数----在加载,insmod的时候调用的函数
static int __init drv_led_init(void)
{
printk("-------------%s-----------------\n",__FUNCTION__);
return platform_driver_register(&pdrv); //注册一个驱动到平台总线中
}
// 3,出口函数---在卸载这个模块,rmmod的时候调用
static void __exit drv_led_exit (void)
{
printk("-------------%s-----------------\n",__FUNCTION__);
platform_driver_unregister(&pdrv);//从平台总线中,移除这个驱动
}
// 声明
module_init(drv_led_init);//声明入口函数是哪一个函数
module_exit(drv_led_exit);//声明哪一个函数是出口函数
MODULE_LICENSE("GPL");
四、程序结果
五、自定义资源
一开始我们代码像下面这样,拿到一个资源之后,我们要对这个资源进行操作,比如用GPIOZ口开灯,它的时钟可能是AHB5,但如果用GPIOF,那么时钟又需要修改成AHB4;再比如GPIOZ的引脚,这个时候我用的是GPIOZ的PIN_5,那我想要用PIN_4的时候又需要修改大量的代码;以及它的ODR输出寄存器,这些易改变的数据我们都需要将它们定义成一个结构体,到时候只需要修改这个结构体中这些数据的值即可而,device.c文件和driver.c文件都不需要改动.(这样的程序可移植性高!)
1、一开始的程序(易改变的数据,以GPIOZ 5为例)
2、全部定义成一个结构体(Platform_data_spex),放进一个头文件specil中,然后再调用
3、在device.c中调用头文件里所需要的数据,定义一个结构体
//这是在定义自定的资源,因为这些资源会随着硬件的变化而改变
struct Platform_data_spex special_data={
.name = "drv_led0",
.minum = 0,
.MODER_SET = 0x1<<10,
.MODER_RESET = 0x3<<10,
.ODR_SET = 0x1<<5,
.ODR_RESET = 0x1<<5,
.RCC_MP_REGISTER = 0,
.RCC_OFFSET = 0x210/4,
};
当我要设置模式的时候,用MODER_SET代替0x1<<10编译到driver.c中,
当我要设置ODR的时候,ODR_SET = 0x1<<5,
比如:
drv_led->gpio->MODER |= (0x1<<10);
drv_led->gpio->MODER |= drv_led->led_pdata->MODER_SET;
drv_led->gpio->ODR &= ~(0x1<<5);
drv_led->gpio->ODR &= ~(drv_led->led_pdata->ODR_RESET);
4、查看数据手册(datasheet),查找每个寄存器所需的移位
以GPIOZ 5为例:
ODR输出寄存器也是这样:
RCC_MP_REGISTER (操作寄存器的位)和OFFSET(偏移量)
5、修改替换后的程序