字符设备驱动高级篇1——注册字符设备驱动的新接口

以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。

一、注册字符设备驱动的接口

在《字符设备驱动基础》里,注册字符设备驱动使用的函数是register_chrdev()函数

该函数的介绍,见博客字符设备驱动基础3——使用register_chrdev()函数注册字符设备。

值得一提的是,该函数同时完成设备号的分配,以及驱动的注册。

二、注册字符设备驱动的接口

1、概述

(1)注册设备驱动的新方法

注册设备驱动的新方法,将以前的register_chrdev()函数完成的工作拆分为两个步骤。

主步骤一:注册设备号

主要涉及register_chrdev_region()函数 或 alloc_chrdev_region()函数

这两个函数的功能都是分配设备号,根据情况选其一即可。

register_chrdev_region()函数,其传参表示想索要某个设备号。

alloc_chrdev_region()函数,是让内核自动分配设备号。

主步骤二:注册设备驱动

主要涉及cdev_add()函数

(2)其他涉及的函数

cdev_init函数:初始化,主要将cdev和fops关联起来。

cdev_alloc函数:分配空间

cdev_del函数:注销设备驱动

unregister_chrdev_region:注销设备号

(3)涉及的新结构体

struct cdev结构体。

(4)注册、注销字符设备驱动的流程

注册字符设备驱动的流程

先使用register_chrdev_region()函数申请设备号,然后使用cdev_alloc函数来给struct cdev结构体分配空间,接着使用cdev_init函数来初始化,最后使用cdev_add()函数注册设备驱动。

注销字符设备驱动的流程

先使用cdev_del()函数注销设备驱动,再使用unregister_chrdev_region()函数注销设备号。

2、新接口的介绍

(1)struct cdev结构体

该结构体定义在文件\include\linux\cdev.h文件中,内容如下:

struct cdev {struct kobject kobj;struct module *owner;const struct file_operations *ops; //文件操作函数集struct list_head list;dev_t dev;  //设备号unsigned int count;
};

其中,dev表示设备号(含主次设备号),它属于dev_t类型(其实就是init类型)。

设备号由主设备号和次设备号组成,在\include\linux\kdev_t.h文件中有这样的宏定义:

#define MAJOR(dev)	((dev)>>8)           //从设备号中提取主设备号
#define MINOR(dev)	((dev) & 0xff)       //从设备号中提取次设备号
#define MKDEV(ma,mi)	((ma)<<8 | (mi)) //由主次设备号得到设备号

(2)register_chrdev_region()函数

此函数定义在/fs/char_dev.c文件中,用于向内核注册(即申请)一组字符设备的设备编号,这就是函数名字带有“region”(区域、范围)的原因。

这一组字符设备的主设备号相同而次设备号不同,所以使用参数提供这组字符设备的统一的名字(name)、这组字符设备的数量(即次设备号的数目,count)、这组字符设备的设备编号的起始编号(from)。

函数的内容如下:

/*** register_chrdev_region() - register a range of device numbers* @from: the first in the desired range of device numbers; must include*        the major number.* @count: the number of consecutive device numbers required* @name: the name of the device or driver.** Return value is zero on success, a negative error code on failure.*/
int register_chrdev_region(dev_t from, unsigned count, const char *name)
{struct char_device_struct *cd;dev_t to = from + count;dev_t n, next;for (n = from; n < to; n = next) {next = MKDEV(MAJOR(n)+1, 0);if (next > to)next = to;cd = __register_chrdev_region(MAJOR(n), MINOR(n),next - n, name);if (IS_ERR(cd))goto fail;}return 0;
fail:to = n;for (n = from; n < to; n = next) {next = MKDEV(MAJOR(n)+1, 0);kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));}return PTR_ERR(cd);
}

参数含义

(1)from

要分配的设备编号范围的初始值(初始,所以才叫from,次设备号常设为0)。

(2)count

连续编号范围(或者说次设备的数目)。

(3)name

设备名称 (通过/proc/devices可以查看到)。

假如主设备号是 200,次设备号有0、1、2、3,则 from 为MKDEV(200,0)(MKDEV用来合成设备号,0表示起始次设备号),count为4(表示有四个次设备号)。

结构体struct char_device_struct的定义

static struct char_device_struct {struct char_device_struct *next;unsigned int major;               //主设备的编号unsigned int baseminor;           //次设备的起始编号int minorct;                      //次设备的数目char name[64];                    //设备(或者说驱动)的名字struct cdev *cdev;	              //指向  字符设备驱动程序描述符  的指针
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];  //#define CHRDEV_MAJOR_HASH_SIZE	255

由该结构体的定义可知,内核中一共有255个主设备号。同一主设备号下的不同设备,它们的次设备号是按起始次设备号递增排序的。这些同主设备号而不同次设备号的设备,统一用一个struct char_device_struct结构体来描述。

总结:使用register_chrdev_region()函数申请设备号时,要事先明确申请哪个主次设备号,这要通过cat /proc/devices来查看哪些主设备号没有被使用。有没有自动分配主次设备号的函数呢?有的,那就是下面的alloc_chrdev_region函数。


(3)alloc_chrdev_region()函数

此函数位于/fs/char_dev.c文件中,用于让内核自动分配设备号。

函数的内容如下:

/*** alloc_chrdev_region() - register a range of char device numbers* @dev: output parameter for first assigned number* @baseminor: first of the requested range of minor numbers* @count: the number of minor numbers required* @name: the name of the associated device or driver** Allocates a range of char device numbers.  The major number will be* chosen dynamically, and returned (along with the first minor number)* in @dev.  Returns zero or a negative error code.*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)
{struct char_device_struct *cd;cd = __register_chrdev_region(0, baseminor, count, name);if (IS_ERR(cd))return PTR_ERR(cd);*dev = MKDEV(cd->major, cd->baseminor);return 0;
}

如果设备号成功分配,则把分配的设备号放入第一个参数dev中(它是输出型参数)。


(4)cdev_init()函数              

此函数位于/fs/char_dev.c文件中,函数的内容如下:

/*** cdev_init() - initialize a cdev structure* @cdev: the structure to initialize* @fops: the file_operations for this device** Initializes @cdev, remembering @fops, making it ready to add to the* system with cdev_add().*/
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{memset(cdev, 0, sizeof *cdev);INIT_LIST_HEAD(&cdev->list);kobject_init(&cdev->kobj, &ktype_cdev_default);cdev->ops = fops;
}

函数主要作用,是将cdev(设备的体现)与fops(驱动的体现)关联起来,即把文件操作结构体指针赋值给cdev的ops。

可以不使用此函数,直接用下面的代码。

//cdev_init(pcdev, &test_fops);pcdev->owner = THIS_MODULE;
pcdev->ops = &test_fops;

(5)cdev_add()函数

此函数位于/fs/char_dev.c文件中,内容如下:

/*** cdev_add() - add a char device to the system* @p: the cdev structure for the device* @dev: the first device number for which this device is responsible* @count: the number of consecutive minor numbers corresponding to this*         device** cdev_add() adds the device represented by @p to the system, making it* live immediately.  A negative error code is returned on failure.*/
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{p->dev = dev;p->count = count;return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}

这个函数的作用是注册设备驱动。

这个函数主要是填充struct cdev结构体变量的成员dev和count。

注意前面在cdev_init函数中,已经填充了struct cdev结构体变量的成员ops。

(6)cdev_alloc()函数

此函数位于/fs/char_dev.c文件中,用于申请(存放struct cdev结构体变量的)空间。

此函数的内容如下:

/*** cdev_alloc() - allocate a cdev structure** Allocates and returns a cdev structure, or NULL on failure.*/
struct cdev *cdev_alloc(void)
{struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);if (p) {INIT_LIST_HEAD(&p->list);kobject_init(&p->kobj, &ktype_cdev_dynamic);}return p;
}

其实也可以直接定义一个struct cdev结构体变量,不过这个变量分配在数据段,整个程序运行期间都存在,不够灵活。

static struct cdev test_cdev;

而采用cdev_alloc()函数来申请(存放struct cdev结构体变量的)空间,这个空间是在堆上的。我们可以在数据段上定义一个struct cdev结构体指针,用来指向cdev_alloc()函数申请的空间,这个指针只占4个字节。

//static struct cdev test_cdev;
//这个变量分配在数据段,整个程序运行期间都存在,不够灵活static struct cdev *pcdev;
//只占4个字节,之后cdev_alloc给它实例化,分配在堆上,可以按需分配。由cdev_del释放
pcdev = cdev_alloc();  // 给pcdev分配内存,指针实例化

 

三、代码示例

1、注册字符设备驱动的代码

// 使用新的cdev接口来注册字符设备驱动,需要2步// 第1步:注册主次设备号mydev = MKDEV(MYMAJOR, 0);//MYMAJOR在这里是200,这里合成了要申请的设备号,即200,0retval = register_chrdev_region(mydev, MYCNT, MYNAME);//这里申请刚才合成的设备号if (retval) {printk(KERN_ERR "Unable to register minors for %s\n", MYNAME);return -EINVAL;}printk(KERN_INFO "register_chrdev_region success\n");// 第2步:注册字符设备驱动cdev_init(&test_cdev, &test_fops);//这步其实可以用其他代码来代替retval = cdev_add(&test_cdev, mydev, MYCNT);//注册字符设备驱动if (retval) {printk(KERN_ERR "Unable to cdev_add\n");return -EINVAL;}printk(KERN_INFO "cdev_add success\n");

2、注销字符设备驱动的代码 

// 使用新的接口来注销字符设备驱动,注销分2步:// 第一步真正注销字符设备驱动用cdev_delcdev_del(&test_cdev);// 第二步去注销申请的主次设备号unregister_chrdev_region(mydev, MYCNT);

3、完整的程序

(1)驱动源代码

#include <linux/module.h>		// module_init  module_exit
#include <linux/init.h>			// __init   __exit
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>	// arch/arm/mach-s5pv210/include/mach/gpio-bank.h
#include <linux/string.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/cdev.h>#define MYMAJOR		200 //注意通过cat /proc/devices查看这个200主设备号是否被占用
#define MYCNT		1 //表示次设备号只有一个
#define MYNAME		"testchar" //表示设备或者说驱动(混在一起的)的名字#define GPJ0CON		S5PV210_GPJ0CON
#define GPJ0DAT		S5PV210_GPJ0DAT#define rGPJ0CON	*((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT	*((volatile unsigned int *)GPJ0DAT)#define GPJ0CON_PA	0xe0200240
#define GPJ0DAT_PA 	0xe0200244unsigned int *pGPJ0CON;
unsigned int *pGPJ0DAT;int mymajor;
static dev_t mydev;
static struct cdev test_cdev;
char kbuf[100];			// 内核空间的bufstatic int test_chrdev_open(struct inode *inode, struct file *file)
{printk(KERN_INFO "test_chrdev_open\n");rGPJ0CON = 0x11111111;rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));		// 亮return 0;
}static int test_chrdev_release(struct inode *inode, struct file *file)
{printk(KERN_INFO "test_chrdev_release\n");rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));return 0;
}ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, \loff_t *ppos)
{int ret = -1;printk(KERN_INFO "test_chrdev_read\n");ret = copy_to_user(ubuf, kbuf, count);if (ret){printk(KERN_ERR "copy_to_user fail\n");return -EINVAL;}printk(KERN_INFO "copy_to_user success..\n");return 0;
}// 写函数的本质就是将应用层传递过来的数据先复制到内核中,
// 然后将之以正确的方式写入硬件完成操作。
static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf,size_t count, loff_t *ppos)
{int ret = -1;printk(KERN_INFO "test_chrdev_write\n");// 使用该函数将应用层传过来的ubuf中的内容拷贝到驱动空间中的一个buf中//memcpy(kbuf, ubuf);		// 不行,因为2个不在一个地址空间中memset(kbuf, 0, sizeof(kbuf));ret = copy_from_user(kbuf, ubuf, count);if (ret){printk(KERN_ERR "copy_from_user fail\n");return -EINVAL;}printk(KERN_INFO "copy_from_user success..\n");if (kbuf[0] == '1'){rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));}else if (kbuf[0] == '0'){rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));}return 0;
}// 自定义一个file_operations结构体变量,并且去填充
static const struct file_operations test_fops = {.owner		= THIS_MODULE,				// 惯例,直接写即可.open		= test_chrdev_open,		// 将来应用open打开这个设备时实际调用的.release	= test_chrdev_release,		// 就是这个.open对应的函数.write 		= test_chrdev_write,.read		= test_chrdev_read,
};// 模块安装函数
static int __init chrdev_init(void)
{	int retval;printk(KERN_INFO "chrdev_init helloworld init\n");/*// 在module_init宏调用的函数中去注册字符设备驱动// major传0进去表示要让内核帮我们自动分配一个合适的空白的没被使用的主设备号// 内核如果成功分配就会返回分配的主设备好;如果分配失败会返回负数mymajor = register_chrdev(0, MYNAME, &test_fops);if (mymajor < 0){printk(KERN_ERR "register_chrdev fail\n");return -EINVAL;}printk(KERN_INFO "register_chrdev success... mymajor = %d.\n", mymajor);
*/	// 使用新的cdev接口来注册字符设备驱动// 新的接口注册字符设备驱动需要2步// 第1步:注册/分配主次设备号mydev = MKDEV(MYMAJOR, 0);retval = register_chrdev_region(mydev, MYCNT, MYNAME);if (retval) {printk(KERN_ERR "Unable to register minors for %s\n", MYNAME);return -EINVAL;}printk(KERN_INFO "register_chrdev_region success\n");// 第2步:注册字符设备驱动cdev_init(&test_cdev, &test_fops);retval = cdev_add(&test_cdev, mydev, MYCNT);if (retval) {printk(KERN_ERR "Unable to cdev_add\n");return -EINVAL;}printk(KERN_INFO "cdev_add success\n");// 使用动态映射的方式来操作寄存器if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON"))return -EINVAL;if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0CON"))return -EINVAL;pGPJ0CON = ioremap(GPJ0CON_PA, 4);pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);*pGPJ0CON = 0x11111111;*pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));		// 亮return 0;
}// 模块卸载函数
static void __exit chrdev_exit(void)
{printk(KERN_INFO "chrdev_exit helloworld exit\n");*pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));	// 解除映射iounmap(pGPJ0CON);iounmap(pGPJ0DAT);release_mem_region(GPJ0CON_PA, 4);release_mem_region(GPJ0DAT_PA, 4);/*	// 在module_exit宏调用的函数中去注销字符设备驱动unregister_chrdev(mymajor, MYNAME);
*/	// 使用新的接口来注销字符设备驱动// 注销分2步:// 第一步真正注销字符设备驱动用cdev_delcdev_del(&test_cdev);// 第二步去注销申请的主次设备号unregister_chrdev_region(mydev, MYCNT);
}module_init(chrdev_init);
module_exit(chrdev_exit);// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");				// 描述模块的许可证
MODULE_AUTHOR("aston");				// 描述模块的作者
MODULE_DESCRIPTION("module test");	// 描述模块的介绍信息
MODULE_ALIAS("alias xxx");			// 描述模块的别名信息

(2)应用层代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>#define FILE	"/dev/test"		
char buf[100];int main(void)
{int fd = -1;int i = 0;fd = open(FILE, O_RDWR);if (fd < 0){printf("open %s error.\n", FILE);return -1;}printf("open %s success..\n", FILE);while (1){memset(buf, 0 , sizeof(buf));printf("请输入 on 或者 off 或者 flash 或者 quit :\n");scanf("%s", buf);if (!strcmp(buf, "on")){write(fd, "1", 1);}else if (!strcmp(buf, "off")){write(fd, "0", 1);}else if (!strcmp(buf, "flash")){for (i=0; i<3; i++){write(fd, "1", 1);sleep(1);write(fd, "0", 1);sleep(1);}}	else if (!strcmp(buf, "quit")){break;}elsebreak;}close(fd);	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/461289.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ural 1129 (求数据)

先记下来。在test 7wa了。如果谁有数据希望可以指点下。thx. 1 // File Name: 1129.cpp2 // Author: Missa3 // Created Time: 2013/3/12 星期二 17:31:534 5 #include<iostream>6 #include<cstdio>7 #include<cstring>8 #include<algorithm>9 #includ…

码农提高工作效率

原文链接http://www.cnblogs.com/huang0925/p/3612741.html 俗话说&#xff0c;天下武功&#xff0c;唯快不破。也就是说要练成天下高手的话&#xff0c;出招速度一定要快&#xff0c;这样才能在江湖上立足&#xff0c;不至于掉了脑袋。而程序员要在IT界混出个名堂&#xff0c;…

mysql分区表mycat_MySQL 中间件之Mycat垂直分表配置

垂直分表就是将一个库下的多个表拆分到多个MySQL实例&#xff0c;实现库压力分流。通过GTID模式复制&#xff0c;db01与db02之间不进行任何连接与复制当前环境&#xff1a;mycat --> db01与db02db01 --> db03db02 --> db04当前垂直分表架构&#xff1a;后端数据库创建…

Oracle Stream配置详细步骤

Oracle Stream配置详细步骤 作者: 杨宝秋,  出处:IT168 1 引言 Oracle Stream功能是为提高数据库的高可用性而设计的&#xff0c;在Oracle 9i及之前的版本这个功能被称为Advance Replication。Oracle Stream利用高级队列技术&#xff0c;通过解析归档日志&#xff0c;将归档日…

字符设备驱动高级篇2——注册字符设备驱动的函数代码分析

以下内容源于朱有鹏嵌入式课程的学习与整理&#xff0c;如有侵权请告知删除。 一、旧接口register_chrdev()函数 上文说到&#xff0c;旧接口register_chrdev()函数内部同时完成了设备号的分配与驱动的注册&#xff0c;现在我们来分析是否真的如此。 1、函数的调用层次关系 …

LR学习视频

0 性能测试常见用语http://www.boobooke.com/v/bbk15771 lr目录分析http://www.boobooke.com/v/bbk15742.1 lr界面分析http://www.boobooke.com/v/bbk17352.2 lr界面分析http://www.boobooke.com/v/bbk17362.3 lr界面分析http://www.boobooke.com/v/bbk17373 lr常用术语http://…

深入浅出mysql gtid_深入理解MySQL GTID

GTID的概念何为GITDGTID(global transaction identifier)是全局事务标识符&#xff0c;在MySQL5.6版本中作为一个超级特性被推出。事务标识不仅对于Master(起源)的服务器来说是惟一的&#xff0c;而且在整个复制拓扑架构来说&#xff0c;也是全局唯一的。1.GTID的格式GTID sou…

winform 64位系统中使用

WINFOR编译成X86的 转载于:https://blog.51cto.com/agilitygod/1419939

字符设备驱动高级篇3——自动创建设备文件

以下内容源于朱有鹏嵌入式课程学习与整理&#xff0c;如有侵权请告知删除。 问题引入 之前在应用层测试驱动源程序时&#xff0c;需要先安装驱动模块&#xff0c;安装驱动模块后会得到一个主设备号&#xff0c;然后在命令行利用mknod命令“mknod /dev/xxx c 主设备号 次设备号”…

long 转为string_面试必问 Redis数据结构底层原理String、List篇

点击关注上方“Java大厂面试官”&#xff0c;第一时间送达技术干货。阅读文本大概需要 8 分钟。前言今天来整理学习下Redis有哪些常用数据结构&#xff0c;都是怎么使用的呢&#xff1f;首先看下全局存储结构。全局存储结构基础你们肯定都知道&#xff0c;redis支持的基础数据结…

wpf 3D学习

最近在看一些关于wpf 3d的效果&#xff0c;研究了一些代码特效&#xff0c;现在和广大博友共享一下. 首先用到的是MeshGeometry3D&#xff0c;msdn上介绍&#xff1a;用于生成三维形状的三角形基元。主要有4个依赖属性&#xff1a;NormalsProperty&#xff0c;PositionsPropert…

字符设备驱动高级篇4——自动创建设备文件的函数代码分析

以下内容源于朱有鹏嵌入式课程的学习与整理&#xff0c;如有侵权请告知删除。 一、概述 设备文件的创建&#xff0c;主要涉及class_create()函数、device_create()函数。 class_create()函数用于自动创建 /sys/class/目录下的xxx目录。 device_create()函数用于自动创建 /dev/…

unicode字符、python乱码问题

http://www.cnblogs.com/BeginMan/archive/2013/08/08/3246619.html#a1 Python常见常用知识点http://blog.csdn.net/tingsking18/article/details/4033645 Unicode和Python的中文处理如何让Python的Unicode字符串支持中文&#xff1f;要想利用Python的Unicode机制处理字符串&…

win10下如何安装vb6.0sp6_Mac如何安装win10系统?Parallels Desktop 15 Mac安装win10系统教程...

Parallels Desktop 15 mac版是mac上非常强大也非常好用的虚拟机软件&#xff0c;最新版本的parallels desktop mac 15针对最新的Windows 10更新和macOS Catalina&#xff08;10.15&#xff09;进行了优化。今天分享的内容就是Parallels Desktop 15 mac版如何安装win10系统。PD虚…

android面试题精选

1.android dvm 的进程和Linux的进程&#xff0c;应用程序的进程是否为同一个概念&#xff1a; 答&#xff1a;dvm是dalivk虚拟机。每一个android应用程序都在自己的进程中运行&#xff0c;都拥有一个dalivk虚拟机实例。而每一个dvm都是在linux的一个进程。所以说可以认为是同一…

字符设备驱动高级篇5——静态映射表的建立过程,动态映射结构体方式操作寄存器

以下内容源于朱有鹏嵌入式课程的学习与整理&#xff0c;如有侵权请告知删除。 补充内容&#xff1a;字符设备驱动基础5——驱动如何操控硬件_天糊土的博客-CSDN博客 一、静态映射表的建立过程 关于“静态映射表的建立”这部分内容&#xff0c;有以下三个关键&#xff1a; &…

python 分布图_python数据分布型图表柱形分布图系列带误差线的柱形图

柱形分布图系列柱形分布图系列使用柱形图的方式展示数据的分布规律&#xff1b;可以借助误差线或散点图&#xff1b;带误差线的柱形图就是使用每个类别的均值作为柱形的高度&#xff1b;再根据每个类别的标准差绘制误差线&#xff1b;缺点&#xff1a;无法显示数据的分布情况&a…

[汇编] 002基础知识-CPU和寄存器

CPU是什么 当然这里的内存不仅仅指电脑上的内存&#xff0c;例如&#xff1a;我的金士顿8G内存&#xff0c;七彩虹1G独显&#xff0c;在这里来说&#xff0c;显卡也是有内存的(寄存器) CPU如何控制其它部件的&#xff1f; 问题&#xff1a;CPU是如何和电脑主机中其它芯片有条不…

Asp.net中页面传值几种方式

页面传值是学习asp.net初期都会面临的一个问题&#xff0c;总的来说有页面传值、存储对象传值、ajax、类、model、表单等。但是一般来说&#xff0c;常用的较简单有QueryString&#xff0c;Session&#xff0c;Cookies&#xff0c;Application&#xff0c;Server.Transfer。  …

字符设备驱动高级篇6——内核提供的读写寄存器接口

以下内容源于朱有鹏嵌入式课程的学习与整理&#xff0c;如有侵权请告知删除。 1、访问寄存器的方式 之前对寄存器的操作&#xff0c;都是先定义指向寄存器的指针&#xff0c;然后再解引用来对寄存器进行操作。这是因为ARM体系中&#xff0c;内存和IO是统一编址的。但是其他体系…