SEO基础

SEO基础

Products

当前位置:首页 > SEO基础 >

在中山市建设局网站上,郑州有哪些专业的网站制作公司?

96SEO 2026-02-19 15:53 0


函数建立cdev

本小节我们将带领大家做一个激动人心的小实验–点亮led。

在中山市建设局网站上,郑州有哪些专业的网站制作公司?

前面我们已经通过操作寄存器的方式点亮了LED本节我们将带领大家进入点亮开发板RGB

LED

最本质的区别就是有无使用操作系统。

有操作系统的存在则大大降低了应用软件与硬件平台的耦合度它充当了我们硬件与应用软件之间的纽带使得应用软件只需要调用驱动程序接口API

就可以让硬件去完成要求的开发而应用软件则不需要关心硬件到底是如何工作的。

这将大大提高我们应用程序的可移植性和开发效率。

设备驱动与底层硬件直接打交道按照硬件设备的具体工作方式读写设备寄存器完成设备的轮询、中断处理、DMA

通信进行物理内存向虚拟内存的映射最终使通信设备能够收发数据使显示设备能够显示文字和画面使存储设备能够记录文件和数据。

在系统中没有操作系统的情况下工程师可以根据硬件设备的特点自行定义接口如对LED

等。

而在有操作系统的情况下设备驱动的架构则由相应的操作系统定义驱动工程师必须按照相应的架构设计设备驱动如在本次实验中必须设计file_operations

无操作系统即裸机时的设备驱动也就是直接操作寄存器的方式控制硬件在这样的系统中虽然不存在操作系统但是设备驱动是必须存在的。

一般情况下对每一种设备驱动都会定义为一个软件模块包含.h

文件和.c

文件前者定义该设备驱动的数据结构并声明外部函数后者进行设备驱动的具体实现。

其他模块需要使用这个设备的时候只需要包含设备驱动的头文件然后调用其中的外部接口函数即可。

这在STM32

有操作系统时的设备驱动反观有操作系统时首先驱动硬件工作的的部分仍然是必不可少的其次我们还需要将设备驱动融入内核。

为了实现这种融合必须在所有的设备驱动中设计面向操作系统内核的接口这样的接口由操作系统规定对一类设备而言结构一致独立于具体的设备。

由此可见当系统中存在操作系统的时候设备驱动变成了连接硬件和内核的桥梁。

操作系统的存在势必要求设备驱动附加更多的代码和功能把单一的驱动变成了操作系统内与硬件交互的模块它对外呈现为操作系统的API。

首先操作系统完成了多任务并发其次操作系统为我们提供了内存管理机制32

位Linux

的内存空间对于应用程序来说应用程序将可使用统一的系统调用接口来访问各种设备通过write()、read()

等函数读写文件就可以访问各种字符设备和块设备而不用管设备的具体类型和工作方式。

内存管理单元MMU

环境直接访问物理内存是很危险的如果用户不小心修改了内存中的数据很有可能造成错误甚至系统崩溃。

为了解决这些问题内核便引入了MMU

MMU

为编程提供了方便统一的内存空间抽象其实我们的程序中所写的变量地址是虚拟内存当中的地址倘若处理器想要访问这个地址的时候MMU

便会将此虚拟地址Virtual

是一个实际的硬件并不是一个软件程序。

他的主要作用是将虚拟地址翻译成真实的物理地址同时管理和保护内存不同的进程有各自的虚拟地址空间某个进程中的程序不能修改另外一个进程所使用的物理地址以此使得进程之间互不干扰相互隔离。

而且我们可以使用虚拟地址空间的一段连续的地址去访问物理内存当中零散的大内存缓冲区。

很多实时操作系统都可以运行在无MMU的CPU

也运行linux

给一些指定的内存块设置了读、写以及可执行的权限这些权限存储在页表当中MMU

会检查CPU

当前所处的是特权模式还是用户模式如果和操作系统所设置的权限匹配则可以访问如果CPU

要访问一段虚拟地址则将虚拟地址转换成物理地址否则将产生异常防止内存被恶意地修改。

CPU

可以运行在虚拟的内存当中虚拟内存一般要比实际的物理内存大很多使得CPU

当没有启用MMU

在读取指令或者访问内存时便会将地址直接输出到芯片的引脚上此地址直接被内存接收这段地址称为物理地址如下图所示。

简单地说物理地址就是内存单元的绝对地址好比你电脑上插着一张8G

个存储单元便是0x0005无论处理器怎样处理物理地址都是它最终的访问的目标。

当CPU

会根据去访问页表地址寄存器然后去内存中找到页表假设只有一级页表的条目从而翻译出实际的物理地址如下图所示。

对于I.MX

发出的地址都是虚拟地址为了实现虚拟地址到物理地址之间的映射MMU

内部有一个专门存放页表的页表地址寄存器该寄存器存放着页表的具体位置用ioremap

映射一段地址意味着使用户空间的一段地址关联到设备内存上这使得只要程序在被分配的虚拟地址范围内进行读写操作实际上就是对设备寄存器的访问。

TLB

Buffer的作用。

由上面的地址转换过程可知当只有一级页表进行地址转换的时候CPU

每次读写数据都需要访问两次内存第一次是访问内存中的页表第二次是根据页表找到真正需要读写数据的内存地址如果使用两级了表那么CPU

每次读写数据都需要访问3

中包含可以直接转换此虚拟地址的地址描述符则会直接使用这个地址描述符检查权限和地址转换如果TLB

中没有这个地址描述符MMU

才会去访问页表并找到地址描述符之后进行权限检查和地址转换然后再将这个描述符填入到TLB

被填满则会使用round-robin

非常复杂在此我们不做过于深入的了解大家只要大概知道它的作用即可感兴趣的同学可以到网上查阅相关资料对于初学者还是建议先掌握全局然后再深挖其中重要的细节千万不能在汪洋大海中迷失了方向。

本小结我们主要用到的是MMU

之后想要读写具体的寄存器物理地址就必须用到物理地址到虚拟地址的转换函数。

地址转换函数

上面提到了物理地址到虚拟地址的转换函数。

包括ioremap()

ioremap

地址映射函数(内核源码/arch/arc/mm/ioremap.c)

void

类型的指针当映射成功后便返回一段虚拟地址空间的起始地址我们可以通过访问这段虚拟地址来实现实际物理地址的读写操作。

ioremap

函数将物理地址转换成虚拟地址之后理论上我们便可以直接读写I/O

内存但是为了符合驱动的跨平台以及可移植性我们应该使用linux

中指定的函数如iowrite8()、iowrite16()、iowrite32()、ioread8()、ioread16()、ioread32()

等去读写I/O

内存而非直接通过映射后的指向虚拟地址的指针进行访问。

读写I/O

列表2:

类型指针的参数指向被映射后的地址返回值为读取到的数据据对于写I/O

而言他们都有两个参数第一个为要写入的数据第二个参数为要写入的地址返回值为空。

与这些函数相似的还有writeb、writew、writel、readb、readw、readl

等在ARM

架构下writexreadx函数与iowritexioreadx有一些区别writexreadx不进行端序的检查而iowritexioreadx会进行端序的检查。

说了这么多大家可能还是不太理解那么我们来举个栗子比如我们需要操作RGB

或者STM32

当中我们是直接看手册查找对应的寄存器然后往寄存器相应的位写入数据0

的亮灭假设已配置好了输出模式以及上下拉等。

前面我们在不带linux

之后我们就要将LED

灯引脚对应的数据寄存器物理地址映射到程序的虚拟地址空间当中然后我们就可以像操作寄存器一样去操作我们的虚拟地址啦其具体代码如下所示。

列表3:

行将物理地址RCC_MP_GPIOENA映射给虚拟地址指针这段地址大小为4

个字节第6

行把值重新写入到被映射后的虚拟地址当中实际是往寄存器中写入了数据

iounmap

取消地址映射函数(内核源码/arch/arc/mm/ioremap.c)

void

从第一章内核模块再到第二章字符设备驱动从理论到实验总算是一切准备就绪让我们开始着手写LED

字符设备结构体它应该包含我们要操作的寄存器地址。

其次是模块的加载卸载函数加载函数需要注册设备卸载函数则需要释放申请的资源。

然后就是file_operations

实验说明

时钟设置引脚复用为GPIO(本节不用)设置引脚属性(上下拉、速率、驱动能力)控制GPIO

引脚输出高低电平

两类寄存器这两类寄存器中每类对时钟的控制又分为使能时钟控制寄存器和失能时钟控制寄存器。

引脚复用GPIO

系类芯片我们需要通过参考手册以及数据手册来确定引脚的复用功能引脚复用相关的信息可以通过数据手册查询

复用寄存器GPIOx_AFRL(GPIO

外设寄存器地址为0x50002000加上对应的偏移即可访问到复用配置寄存器。

通过配置对应端口的寄存器即可设置对应引脚复用功能。

引脚属性

引脚的模式可为输入模式、输出模式、复用模式、模拟模式。

GPIOx_OTYPER输出类型寄存器用以设置GPIO

引脚的输出模式可为推挽输出、开漏输出。

GPIOx_OSPEEDR速度寄存器用以设置GPIO

引脚的输出速度等级可为低、中、高、非常高。

GPIOx_PUPDR上下拉配置寄存器用以设置GPIO

引脚的上下拉状态可为不上下拉、上拉、下拉。

GPIOx_IDR输入寄存器用以读取GPIO

引脚的输入状态可读取为0、1。

GPIOx_ODR输出寄存器当IO

硬件原理以及寄存器配置到此为止更多硬件上的信息可以查看原理图和芯片手册。

代码讲解

本章的示例代码目录为linux_driver/led_cdev/

定义GPIO

资源物理地址在后面需要将这些寄存器物理地址映射到虚拟地址上供配置使用。

编写LED

为3。

在初始化结构体的时候我们以“.”“变量名字”的形式来访问且初始化结构体变量的初始化结构体变量的时候要以“”隔开使用这种方式简单明了方便管理数据结构中的成员。

行LED

的物理寄存器地址对应上调用alloc_chrdev_region()

的好处在于不必自己费时间去查看那些是未被占用的设备号避免了设备号重复问题调用class_create()

函数创建一个RGB

内核系统设备驱动程序模型中移除一个设备并删除/sys/devices/virtual

列表8:

映射模式寄存器物理地址到虚拟地址led_cdev[0].va_otyper

4);

映射输出类型寄存器物理地址到虚拟地址led_cdev[0].va_ospeedr

4);

映射速度配置寄存器物理地址到虚拟地址led_cdev[0].va_pupdr

ioremap(GPIOA_PUPDR,

映射上下拉寄存器物理地址到虚拟地址led_cdev[0].va_bsrr

ioremap(GPIOA_BSRR,

映射置位寄存器物理地址到虚拟地址led_cdev[1].va_moder

ioremap(GPIOG_MODER,

led_chrdev_fops);led_cdev[i].dev.owner

THIS_MODULE;cur_dev

1);device_create(led_chrdev_class,

NULL,

module_init(led_chrdev_init);static

__exit

{iounmap(led_cdev[i].va_moder);

释放模式寄存器虚拟地址iounmap(led_cdev[i].va_otyper);

释放输出类型寄存器虚拟地址iounmap(led_cdev[i].va_ospeedr);

释放速度配置寄存器虚拟地址iounmap(led_cdev[i].va_pupdr);

释放上下拉寄存器虚拟地址iounmap(led_cdev[i].va_bsrr);

释放置位寄存器虚拟地址}for

i);device_destroy(led_chrdev_class,

cur_dev);cdev_del(led_cdev[i].dev);}unregister_chrdev_region(devno,

DEV_CNT);class_destroy(led_chrdev_class);

module_exit(led_chrdev_exit);第9-25

行初始化LED

led_chrdev,dev);filp-private_data

struct

led_cdev-led_pin));writel(val,led_cdev-va_moder);//

设置输出类型寄存器推挽模式val

(2*led_cdev-led_pin));writel(val,led_cdev-va_pupdr);//

设置置位寄存器默认输出低电平val

函数的实现函数很重要下面我们来详细分析一下该函数具体做了哪些工作。

container_of()

这个函数打交道所以特意拿出来和大家分享一下其实这个函数功能不多但是如果单靠自己去阅读内核源代码分析那可能非常难以理解编写内核源代码的大牛随便两行代码都会让我们看的云深不知处分析内核源代码需要我们有很好的知识积累以及技术沉淀。

下面我简单跟大家讲解一下container_of()

列表10:

函数位于…/ebf_linux_kernel/driver/gpu/drm/mkregtable.c

#define

ptr结构体变量中某个成员的地址type结构体类型member该结构体变量的具体名字

返回值结构体type

需要注意的是它们的大小都是以字节为单位计算的container_of()函数的如下

判断ptr

指向设备结构体其保存了用户自定义设备结构体的地址。

自定义结构体的地址被保存在private_data

后可以通过读、写等操作通过该私有数据去访问设备结构体中的成员这样做体现了linux

其实ioremap()

函数的作用都是一样的只是对LED灯所用到的时钟控制寄存器做了地址映射这样我们便可以通过操作程序中的虚拟地址来间接的控制物理寄存器我们在驱动程序描述寄存器不利于驱动模块的灵活使用后几个章节我们会带领大家通过设备树设备树插件的方式去描述寄存器及其相关属性在此先埋下伏笔循序渐进顺腾摸瓜使大家能够真正理解并掌握linux

和iowrite32()

口、电气属性、输入输出方向以及输出的高低电平等等一般我们访问某个地址时都是先将该地址的数据读取到一个变量中然后修改该变量最后再将该变量写入到原来的地址当中。

注意我们在操作这段被映射后的地址空间时应该使用linux

访问函数如iowrite8()、iowrite16()、iowrite32()、ioread8()、ioread16()、ioread32()

等这里再强调一遍即使理论上可以直接操作这段虚拟地址了但是Linux

中write

*)filp-private_data;kstrtoul_from_user(buf,

tmp,

再分析该函数之前我们先分析一下内核中提供的kstrtoul()

函数理解kstrtoul()

函数解析内核源码/include/linux/kernel.h

static

__builtin_types_compatible_p(unsigned

long,

base转换基数如果base0则函数会自动判断字符串的类型且按十进制输出比如“0xa”就会被当做十进制处理大小写都一样输出为10。

如果是以0

返回值该函数转换成功后返回0溢出将返回-ERANGE解析出错返回-EINVAL。

理解完kstrtoul()函数后想必大家已经知道kstrtoul_from_user()

kstrtoul_from_user()

为要转换数据的大小base转换基数如果base0则函数会自动判断字符串的类型且按十进制输出比如“0xa”就会被当做十进制处理大小写都一样输出为10。

如果是以0

开头则会被解析为八进制数否则将会被解析成小数res一个指向被转换成功后的结果的地址。

返回值

多了一个参数count因为用户空间是不可以直接访问内核空间的所以内核提供了kstrtoul_from_user()

函数以实现用户缓冲区到内核缓冲区的拷贝与之相似的还有copy_to_user()copy_to_user()

间的拷贝。

如果你使用的内存类型没那么复杂便可以选择使用put_user()

或者get_user()

函数的主要任务是清理未结束的输入输出操作释放资源用户自定义排他标志的复位等。

前面我们用ioremap()

将物理地址空间映射到了虚拟地址空间当我们使用完该虚拟地址空间时应该记得使用iounmap()

函数将它释放掉。

不过我们在驱动模块退出的时候才进行释放这里我们不做操作。

列表14:

到这里我们的代码已经分析完成了下面时本驱动的完整代码由于前面已经带领大家详细的分析了一遍所以我把完整代码的注释给去掉了希望你能够会想起每个函数的具体作用。

led_cdev.c

完整代码位于…linux_driver/led_cdev/led_cdev.c

#include

led_chrdev,dev);filp-private_data

struct

ioread32(led_cdev-va_moder);val

~((unsigned

led_cdev-led_pin));iowrite32(val,led_cdev-va_moder);//

设置输出类型寄存器推挽模式val

ioread32(led_cdev-va_otyper);val

~((unsigned

led_cdev-led_pin);iowrite32(val,

设置输出速度寄存器高速val

ioread32(led_cdev-va_ospeedr);val

~((unsigned

led_cdev-led_pin));iowrite32(val,

设置上下拉寄存器上拉val

ioread32(led_cdev-va_pupdr);val

~((unsigned

(2*led_cdev-led_pin));iowrite32(val,led_cdev-va_pupdr);//

设置置位寄存器默认输出低电平val

*)filp-private_data;kstrtoul_from_user(buf,

tmp,

物理地址到虚拟地址led_cdev[0].va_ospeedr

4);

led_chrdev_fops);led_cdev[i].dev.owner

THIS_MODULE;cur_dev

1);device_create(led_chrdev_class,

NULL,

}module_init(led_chrdev_init);static

__exit

{iounmap(led_cdev[i].va_moder);

释放模式寄存器虚拟地址iounmap(led_cdev[i].va_otyper);

释放输出类型寄存器虚拟地址iounmap(led_cdev[i].va_ospeedr);

释放速度配置寄存器虚拟地址iounmap(led_cdev[i].va_pupdr);

释放上下拉寄存器虚拟地址iounmap(led_cdev[i].va_bsrr);

释放置位寄存器虚拟地址}for

i);device_destroy(led_chrdev_class,

cur_dev);cdev_del(led_cdev[i].dev);}unregister_chrdev_region(devno,

DEV_CNT);class_destroy(led_chrdev_class);}module_exit(led_chrdev_exit);MODULE_AUTHOR(embedfire);

可能会被系统占用在使用前请根据需要改/boot/uEnv.txt

子系统。

引脚被占用后设备树可能无法再加载或驱动中无法再申请对应的资源。

方法参考如下

busy”或者运行代码卡死等等现象请按上述情况检查并按上述步骤操作。

如出现Permission

或类似字样请注意用户权限大部分操作硬件外设的功能几乎都需要root

LED

KERNEL_DIR../ebf_linux_kernel/build_image/build

ARCHarm

CROSS_COMPILEarm-linux-gnueabihf-

export

make编译成功后实验目录下会生成”led_cdev.ko”的驱动模块文件和”led_cdev_test”的应用程序。

程序运行结果

led_cdev.ko然后我们可以在/dev/目录下找到led_chrdev0、led_chrdev1、led_chrdev2

来控制LED

这个时候我们再回味一下设备驱动的作用。

当我们开发一款嵌入式产品时产品的设备硬件发生变动的时候我们就只需要更改驱动程序以提供相同的API而不用去变动应用程序就能达到同样的效果这将减少多少开发成本呢。

参考资料嵌入式Linux



SEO优化服务概述

作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。

百度官方合作伙伴 白帽SEO技术 数据驱动优化 效果长期稳定

SEO优化核心服务

网站技术SEO

  • 网站结构优化 - 提升网站爬虫可访问性
  • 页面速度优化 - 缩短加载时间,提高用户体验
  • 移动端适配 - 确保移动设备友好性
  • HTTPS安全协议 - 提升网站安全性与信任度
  • 结构化数据标记 - 增强搜索结果显示效果

内容优化服务

  • 关键词研究与布局 - 精准定位目标关键词
  • 高质量内容创作 - 原创、专业、有价值的内容
  • Meta标签优化 - 提升点击率和相关性
  • 内容更新策略 - 保持网站内容新鲜度
  • 多媒体内容优化 - 图片、视频SEO优化

外链建设策略

  • 高质量外链获取 - 权威网站链接建设
  • 品牌提及监控 - 追踪品牌在线曝光
  • 行业目录提交 - 提升网站基础权威
  • 社交媒体整合 - 增强内容传播力
  • 链接质量分析 - 避免低质量链接风险

SEO服务方案对比

服务项目 基础套餐 标准套餐 高级定制
关键词优化数量 10-20个核心词 30-50个核心词+长尾词 80-150个全方位覆盖
内容优化 基础页面优化 全站内容优化+每月5篇原创 个性化内容策略+每月15篇原创
技术SEO 基本技术检查 全面技术优化+移动适配 深度技术重构+性能优化
外链建设 每月5-10条 每月20-30条高质量外链 每月50+条多渠道外链
数据报告 月度基础报告 双周详细报告+分析 每周深度报告+策略调整
效果保障 3-6个月见效 2-4个月见效 1-3个月快速见效

SEO优化实施流程

我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:

1

网站诊断分析

全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。

2

关键词策略制定

基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。

3

技术优化实施

解决网站技术问题,优化网站结构,提升页面速度和移动端体验。

4

内容优化建设

创作高质量原创内容,优化现有页面,建立内容更新机制。

5

外链建设推广

获取高质量外部链接,建立品牌在线影响力,提升网站权威度。

6

数据监控调整

持续监控排名、流量和转化数据,根据效果调整优化策略。

SEO优化常见问题

SEO优化一般需要多长时间才能看到效果?
SEO是一个渐进的过程,通常需要3-6个月才能看到明显效果。具体时间取决于网站现状、竞争程度和优化强度。我们的标准套餐一般在2-4个月内开始显现效果,高级定制方案可能在1-3个月内就能看到初步成果。
你们使用白帽SEO技术还是黑帽技术?
我们始终坚持使用白帽SEO技术,遵循搜索引擎的官方指南。我们的优化策略注重长期效果和可持续性,绝不使用任何可能导致网站被惩罚的违规手段。作为百度官方合作伙伴,我们承诺提供安全、合规的SEO服务。
SEO优化后效果能持续多久?
通过我们的白帽SEO策略获得的排名和流量具有长期稳定性。一旦网站达到理想排名,只需适当的维护和更新,效果可以持续数年。我们提供优化后维护服务,确保您的网站长期保持竞争优势。
你们提供SEO优化效果保障吗?
我们提供基于数据的SEO效果承诺。根据服务套餐不同,我们承诺在约定时间内将核心关键词优化到指定排名位置,或实现约定的自然流量增长目标。所有承诺都会在服务合同中明确约定,并提供详细的KPI衡量标准。

SEO优化效果数据

基于我们服务的客户数据统计,平均优化效果如下:

+85%
自然搜索流量提升
+120%
关键词排名数量
+60%
网站转化率提升
3-6月
平均见效周期

行业案例 - 制造业

  • 优化前:日均自然流量120,核心词无排名
  • 优化6个月后:日均自然流量950,15个核心词首页排名
  • 效果提升:流量增长692%,询盘量增加320%

行业案例 - 电商

  • 优化前:月均自然订单50单,转化率1.2%
  • 优化4个月后:月均自然订单210单,转化率2.8%
  • 效果提升:订单增长320%,转化率提升133%

行业案例 - 教育

  • 优化前:月均咨询量35个,主要依赖付费广告
  • 优化5个月后:月均咨询量180个,自然流量占比65%
  • 效果提升:咨询量增长414%,营销成本降低57%

为什么选择我们的SEO服务

专业团队

  • 10年以上SEO经验专家带队
  • 百度、Google认证工程师
  • 内容创作、技术开发、数据分析多领域团队
  • 持续培训保持技术领先

数据驱动

  • 自主研发SEO分析工具
  • 实时排名监控系统
  • 竞争对手深度分析
  • 效果可视化报告

透明合作

  • 清晰的服务内容和价格
  • 定期进展汇报和沟通
  • 效果数据实时可查
  • 灵活的合同条款

我们的SEO服务理念

我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。

提交需求或反馈

Demand feedback