Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
OpenHarmony
Docs
提交
c05cf492
D
Docs
项目概览
OpenHarmony
/
Docs
大约 2 年 前同步成功
通知
161
Star
293
Fork
28
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
D
Docs
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
未验证
提交
c05cf492
编写于
1月 04, 2022
作者:
O
openharmony_ci
提交者:
Gitee
1月 04, 2022
浏览文件
操作
浏览文件
下载
差异文件
!995 Add new OpenHarmony 64bits and HDF Coding Guide
Merge pull request !995 from Zhangchunxin/master
上级
b0001fd4
301572b4
变更
3
隐藏空白更改
内联
并排
Showing
3 changed file
with
1227 addition
and
0 deletion
+1227
-0
zh-cn/contribute/OpenHarmony-64bits-coding-guide.md
zh-cn/contribute/OpenHarmony-64bits-coding-guide.md
+590
-0
zh-cn/contribute/OpenHarmony-hdf-coding-guide.md
zh-cn/contribute/OpenHarmony-hdf-coding-guide.md
+635
-0
zh-cn/contribute/贡献代码.md
zh-cn/contribute/贡献代码.md
+2
-0
未找到文件。
zh-cn/contribute/OpenHarmony-64bits-coding-guide.md
0 → 100755
浏览文件 @
c05cf492
# OpenHarmony 32/64位可移植编程规范
## 前言
### 目的
OpenHarmony的目标是面向全场景、全连接、全智能时代,基于开源的方式,搭建一个智能终端设备操作系统的框架和平台,促进万物互联产业的繁荣发展。具有“硬件互助,资源共享”、“一次开发,多端部署”、“统一OS,弹性部署”的技术特性。
OpenHarmony支持三种系统类型:
1.
轻量系统(mini system),面向从MCU类处理器(例如Arm Cortex-M、RISC-V 32位)的轻量设备,硬件资源极其有限,支持的设备最小内存为128KiB;
2.
小型系统(small system),面向应用处理器(例如Arm Cortex-A 64位)的设备,支持的设备最小内存为1MiB
3.
标准系统(standard system),面向应用处理器(例如Arm Cortex-A 64位)的设备,支持的设备最小内存为128MiB
因此,OpenHarmony的代码运行在32位/64位的设备上。对系统代码的可移植性、32位/64位运行模式下的编码需要一定的规约。本文以此为初衷,结合OpenHarmony的特点,拟定了相关编程规约,用于指导代码移植和64位编码,提升代码的规范性及可移植能力,供研发人员参考。
### 适用范围
用户态和内核态的C、C++代码,不区分语言的标准。
### 32位/64位系统的类型差异
#### 数据类型差异
大部分的32位系统采用的是ILP32,即int、long和pointer是32位长度。大部分的64位系统采用的是LP64,即long、long long、pointer是64位长度。Windows系统采用的是LLP64,即long long和pointer是64位长度。各系统基本数据长度对比如下表所示:
|
**TYPE**
|
**ILP32**
|
**LP64**
|
**LLP64**
|
**LP32**
|
**ILP64**
|
| --------- | --------- | -------- | --------- | -------- | --------- |
| char | 1 | 1 | 1 | 1 | 1 |
| short | 2 | 2 | 2 | 2 | 2 |
| int | 4 | 4 | 4 | 2 | 8 |
| long |
**4**
|
**8**
|
**4**
| 4 | 8 |
| long long | 8 | 8 | 8 | 8 | 8 |
| float | 4 | 4 | 4 | 4 | 4 |
| double | 8 | 8 | 8 | 8 | 8 |
| size_t |
**4**
|
**8**
|
**8**
| 4 | 8 |
| pointer |
**4**
|
**8**
|
**8**
| 4 | 8 |
上表中只包含了部分基本类型,下表分别将ILP32和LP64的siziof和print
`进行对比,展示了更全面的常量和类型对应的差异:
| Type | ILP32 sizeof | ILP32 print | LP64 sizeof | LP64 print | 备注 |
| ------------------ | ------------ | ----------- | ----------- | ---------- | ------ |
| bool | 1 | %u | 1 | %u | C++ |
| char | 1 | %d或%c | 1 | %d或%c | |
| unsigned char | 1 | %u | 1 | %u | |
| short | 2 | %d | 2 | %d | |
| unsigned short | 2 | %u | 2 | %u | |
| int | 4 | %d | 4 | %d | |
| unsigned int | 4 | %u | 4 | %u | |
| long | 4 | %ld | **8** | %ld | 有差异 |
| unsigned long | 4 | %lu | **8** | %lu | 有差异 |
| long int | 4 | %ld | **8** | %ld | 有差异 |
| unsigned long int | 4 | %lu | **8** | %lu | 有差异 |
| long long | 8 | %lld | 8 | %lld | |
| unsigned long long | 8 | %llu | 8 | %llu | |
| type * | 4 | %p | **8** | %p | 有差异 |
| pid_t | 4 | %d | 4 | %d | |
| socklen_t | 4 | %u | 4 | %u | |
| off_t | 4 | %zd | **8** | %zd | 有差异 |
| time_t | 4 | %zd | 8 | %zd | 有差异 |
| pthread_t | 4 | %zu | **8** | %zu | 有差异 |
| size_t | 4 | %zu | 8 | %zu | 有差异 |
| ssize_t | 4 | %zd | **8** | %zd | 有差异 |
#### 数据结构对齐的差异
##### 包含指针数据结构对齐的变化
```c
typedef struct tagFoo {
void *p;
uint32_t i;
} Foo;
```
在32位系统上,指针长度为4,Foo 4字节对齐,sizeof(Foo) 等于8,在64位系统上,指针长度为8,Foo 8字节对齐,sizeof(Foo) 等于16。
##### 包含64位整形的数据结构对齐的变化
```c
typedef struct tagFoo {
uint64_t p;
uint32_t i;
} Foo;
```
虽然uint64_t是定长的,但由于对齐的存在,Foo的大小也是不同的。在32位系统上,Foo 4字节对齐,sizeof(Foo) 等于12,在64位系统上,Foo 8字节对齐,sizeof(Foo) 等于16。uint64_t替换为double,上述结论依然成立。
### 约定
**规则**:编程时必须遵守的约定(must)
**建议**:编程时应该遵守的约定(should)
## 编程指导
### 总则
#### 【规则】开发者贡献的代码应当遵循此规范,编写出可同时应用于32位和64位的代码
【说明】由于OpenHarmony会长期同时存在32位的运行环境和64位的运行环境,而为了代码的一致性,开发者在编码时需要充分考虑代码的可移植能力。
### 数据类型定义和格式化
#### 【规则】应当使用统一定义的数据类型定义变量,无特殊意义或要求应当避免自行定义基本数据类型
【说明】基于可移植性要求,在32位和64位条件下,可变长度的数据类型可能导致兼容性错误,为简单清晰,要求采用归一清晰的数据类型进行定义。基于当前的要求,定义下列基础数据类型:
| 类型定义 | ILP32 | LP64 | PRINT | 使用场景及代替类型 |
| ---------------- | ----- | ----- | ------- | ------------------------------------------------------------ |
| void | - | - | - | void,无类型,仅用于占位和通用指针定义 |
| char | 1 | 1 | %c | 对于字符串、数组直接使用原生char |
| int8_t | 1 | 1 | %d | 对于1字节整型使用int8_t,uint8_t |
| uint8_t | 1 | 1 | %u | 对于1字节整型使用int8_t,uint8_t |
| int16_t | 2 | 2 | %d | 代替short |
| uint16_t | 2 | 2 | %u | 代替unsigned short |
| int32_t | 4 | 4 | %d | 代替int |
| uint32_t | 4 | 4 | %u | 代替unsigned int |
| int64_t | 8 | 8 | %PRId64 | 代替long long,宏实现代码兼容 |
| uint64_t | 8 | 8 | %PRIu64 | 代替unsigned longlong,宏实现代码兼容 |
| float | 4 | 4 | %f | 单精度浮点数 |
| double | 8 | 8 | %lf | 双精度浮点数 |
| bool | 1 | 1 | %d | 布尔类型 |
| uintptr_t | **4** | **8** | %zu | 会根据32位和64位的不同定义为不同的长度,用于可能存储指针的场景 |
| type * | **4** | **8** | %p | type *,可变长度类型,与uintptr_t等价,存在类型转换时建议使用uintptr_t |
| nullptr_t | **4** | **8** | %p | 指针初始化 |
| pid_t | 4 | 4 | %d | Linux内置,固定长度 |
| socklen_t | 4 | 4 | %u | Linux内置,固定长度 |
| off_t/time_t | **4** | **8** | %zd | 可变长类型,有符号 |
| size_t/pthread_t | **4** | **8** | %zu | 可变长度类型,无符号,仅用于调用库函数的兼容性要求(比如底层API中使用了size_t) |
上述类型定义在stddef.h(C)和cstdint(C++)标准库中,在采用#define重定义相关类型时,其源头应来自于上述类型。
非特殊情况,不要使用非标准类型。禁止定义通用基础类型,除非定义的类型有明确的特定含义。对于涉及到第三方接口及API调用中使用的基础数据类型,以相关专项规则为准。
【示例】非必要禁止自定义基础数据类型:
```c
// 禁止使用下面代码来重定义
typedef unsigned int UINT32;// 此定义禁止使用
// 已经有内置的uint32_t完全可代替上述定义,因此上述定义没有存在的必要。但如果定义的类型有明确的专用意义,则可以保留定义:
typedef uint32_t DB_TABLE_ID; // 此定义可以保留
```
【示例】下面2个类型的长度与通常理解不一致,禁止使用:
| 类型定义 | ILP32 | LP64 | PRINT | 使用场景及代替类型 |
| -------- | ------ | ----- | ----- | ---------------------- |
| float_t | **12** | **4** | - | 长度不合常理,禁止使用 |
| double_t | **12** | **8** | - | 长度不合常理,禁止使用 |
#### 【建议】应当避免使用非统一定义的可变长类型定义变量,为适配平台、第三方代码接口等使用,需要特殊说明
【说明】原生的long、int、short、size_t等类型,在64位和32位下其长度不确定,在编码时容易疏忽而导致代码隐患。如果将此类型用于外部存储或通信,则很容易导致系统间的不兼容,因此无特殊需求原则上应当避免使用这些类型。
【例外】如果因为平台、第三方、库函数等原因,可以有限使用。使用这些类型定义时需要增加说明方便理解。
【示例】
```c
long var;
// 该定义在64位下为8bytes,在32位下为4bytes,存在歧义,建议修改为uint32_t或uint64_t。
```
#### 【规则】避免使用uchar类型定义变量
【说明】uchar或unsigned char用来定义字符串是一种不规范用法。对当前代码中已经存在的使用,需要修改为char类型(确认是字符串)或uint8_t(确认是无符号整数)。
对于涉及到8bit编码的非ANSI字符序列,建议仍然使用char类型来定义。C++情况下,可使用wchar等宽字符类型定义。
#### 【规则】当需要采用整型变量来存储指针时,变量应该定义成uintptr_t以适应不同的位宽
【说明】uintptr_t类型用于用于存储指针长度级别的数据,其长度在32位和64位可自动适应。
【示例】
```c
uintptr_t sessionPtr;
// 将指针存储为变量时,强转和左值都应定义为uintptr_t,以适配不同场景字长的变化
sessionPtr = (uintptr_t) GetMemAddress();
```
#### 【建议】函数入参或返回值定义的类型与变量类型发生不匹配时候,需要谨慎处理,以保证按照赋值和类型转换的规则进行转换后的结果正确
【说明】如果不可避免出现了类型不一致的情况,需要谨慎进行类型转换,确保转换后的结果满足实际应用需求。
【示例】
```c
long function (long l);
int main () {
int i = -2;
unsigned int k = 1U;
long n = function(i + k);
}
```
【注释】上面这段代码在 64 位系统上会失败,因为表达式 (i + k) 是一个无符号的 32 位表达式,在将其转换成 long 类型时,符号并没有得到扩展。入参的结果是不正确的。解决方案是将其中一个操作数强制转换成 64 位的类型。
#### 【规则】打印64位的整数请使用%PRId64,%PRIu64,%PRIx64等64位兼容宏进行格式化输出,不允许使用%d,%ld,%zd,%x,%lx等不兼容输出
【说明】如果待格式化的数据明确为64位类型(定义为uint64_t),其输出格式化应当采用下述方法:
```c
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
int main()
{
uint64_t a = 0x1234567fffffff;
printf("a = %"PRIx64"\n", a);
return 0;
}
```
上述输出代码,在32位和64位环境下均能够正常输出64位长度数字。如果采用其它格式处理,都存在兼容性问题,需要注意避免,如下表:
| 格式化方法 | ILP32构建 | ILP32结果 | LP64构建 | LP64结果 | 结论 |
| ---------- | -------------- | --------- | -------------- | -------- | ---------- |
| %lx | 类型不匹配告警 | 错误 | 无告警 | 正确 | **不兼容** |
| %zx | 类型不匹配告警 | 错误 | 无告警 | 正确 | **不兼容** |
| %llx | 无告警 | 正确 | 类型不匹配告警 | 正确 | **不兼容** |
| %p | 类型不匹配告警 | 错误 | 类型不匹配告警 | 正确 | **不兼容** |
| %PRIx64 | 无告警 | 正确 | 无告警 | 正确 | 兼容 |
【示例】类型不匹配告警信息示例:
```bash
# 32位编译
format ‘%lx’ expects argument of type ‘long unsigned int’, but argument 2 has type ‘uint64_t {aka long long unsigned int}’
format ‘%zx’ expects argument of type ‘size_t’, but argument 2 has type ‘uint64_t {aka long long unsigned int}’
format ‘%p’ expects argument of type ‘void *’, but argument 2 has type ‘uint64_t {aka long long unsigned int}’
# 64位编译
format ‘%llx’ expects argument of type ‘long long unsigned int’, but argument 2 has type ‘uint64_t {aka long unsigned int}’
format ‘%p’ expects argument of type ‘void *’, but argument 2 has type ‘uint64_t {aka long unsigned int}’
```
#### 【规则】打印输出可变类型数据,对齐时要考虑数据长度并预留足够空间
【说明】32位下指针及size_t长度最大只有8位(16进制)或10位(10进制),但在64位系统下,其最大宽度可达到20位,因此在打印输出时需要充分考虑其范围,避免打印因不对齐而影响用户体验。
#### 【规则】使用常量时,禁止采用L/UL作后缀,允许增加U后缀指定为unsigned int类型,允许增加LL/ULL后缀指定其长度为64位
【说明】无后缀的常量缺省为int类型;采用L/UL为后缀时,在32位和64位系统下,长度可能发生变化;确定需要64位时,采用LL/ULL为后缀定义以确保在32位和64位系统下均为64位长度。
| 常量定义 | ILP32 | LP64 | 使用场景 |
| -------------- | --------- | ----- | ------------------------------------------------------------ |
| 1 | 4 | 4 | 默认为int32_t类型,长度固定 |
| 1U | 4 | 4 | 默认为uint32_t类型,长度固定 |
| 1L | **4** | **8** | 后缀为L或UL,长度不同,应当避免使用 |
| 1UL | **4** | **8** | 后缀为L或UL,长度不同,应当避免使用 |
| 1LL | 8 | 8 | 默认为int64_t类型,长整形数据,直接使用LL,确定为64位,长度固定 |
| 1ULL | 8 | 8 | uint64_t类型,无符号长整形数据 |
| 0x7FFFFFFF | 4 | 4 | 不携带附加符号的数字,不超过int32_t的范围,默认为int32_t类型 |
| 0x7FFFFFFFL | **4** | **8** | 长度不同,避免使用 |
| 0x7FFFFFFFLL | 8 | 8 | 长度固定 |
| 0x80000000 | 4 | 4 | 小于uint32_t范围,类型为uint32_t类型 |
| 0x80000000L | **4** | **8** | 后缀为L,小于uint32_t范围,增加该参数没有意义,应当避免使用 |
| 0x80000000LL | 8 | 8 | 增加LL后缀,小于uint32_t范围,长度固定 |
| 0x8000000000 | **NA或8** | **8** | 无后缀,超过uint32_t的范围,编译器默认为LL或无效,64位下固定为uint64_t类型 |
| 0x8000000000L | **NA或8** | **8** | 后缀为L,对超过uint32_t的范围常数,增加该参数没有意义,应当避免使用 |
| 0x8000000000LL | 8 | 8 | 默认为LL,uint64_t类型 |
从上表中可看出,使用L或UL后缀的常量,其长度在32位和64位下发生变化,不利于代码的可移植性,因此禁止使用这个后缀。
【示例】
```c
// 下面定义中的UL是没有意义的,在32位系统会出错,在64位系统不需要
#define YYY_START_ADDRESS(XXX_START_ADDR + 0x80000000UL)
```
#### 【规则】应当使用标准头文件中定义的宏常量,避免自行定义
【说明】C(stdint.h)和C++(cstdint.h)的标准头文件中均有定义最大值/最小值的宏常量,源文件中引用即可,避免自行定义。
【示例】
```c
#include <cstdio>
#include <cinttypes>
int main()
{
std::printf("%zu\n", sizeof(std::int64_t));
std::printf("%s\n", PRId64);
std::printf("%+" PRId64 "\n", INT64_MIN);
std::printf("%+" PRId64 "\n", INT64_MAX);
std::int64_t n = 7;
std::printf("%+" PRId64 "\n", n);
}
```
#### 【规则】应当使用统一的定义表示常量的无效值,避免直接使用全F等表示方法以适应不同的长度
【说明】同一个数字常量值在64位系统和32位系统上,按照32位系统有无符号类型,理解的结果可能不一样;特别是32位系统上最高bit位为1的,在32位系统上按照有符号类型理解成一个负数,在64位系统上是一个正数。要求统一使用typedef.h中的统一定义,表示数据的无效值。
```c
// 使用typedef.h中的定义
#define INVALID_INT8((int8_t)(-1))
#define INVALID_UINT8((uint8_t)(-1))
#define INVALID_INT16((int16_t)(-1))
#define INVALID_UINT16((uint16_t)(-1))
#define INVALID_INT32((int32_t)(-1))
#define INVALID_UINT32((uint32_t)(-1))
#define INVALID_INT64((int64_t)(-1))
#define INVALID_UINT64((uint64_t)(-1))
```
【示例】
```c
// 在32位系统上,n是一个负数
long n = 0xFFFFFFFF;
// 在64位系统上,n是一个正数,实际值为0x00000000FFFFFFFF。
long n = 0xFFFFFFFF;
```
#### 【建议】对不会超过32位空间大小的临时变量,建议采用uint32_t定义
【说明】定义的变量不需要特别关注其类型,可采用默认的uint32_t类型,以减少因类型不一致导致的大量强制类型转换。
### 结构体对齐与填充
#### 【规则】代码中禁止采用常量硬编码来指定变量长度、结构体长度,应当使用sizeof等内建类型来获取
【说明】sizeof可自动计算相关变量和结构的长度,避免硬编码错误;同时在编译时即完成了所属变量长度的计算,不会影响运行性能。
在64位系统下默认对齐方式是8字节对齐,使用sizeof()获取结构体长度来取代硬编码,以避免因结构对齐导致的长度计算错误。
联合在使用时,需要特别关注到其长度在64位和32位下的不同,避免在计算长度时出现错误。特别是按照最大长度进行计算和空间申请。
【示例】未采用sizeof计算结构长度,可能导致内存空间不足。
```c
int32_t *p;
p = (int32_t *)malloc(4 * ELEMENTS_NUMBER);
// 这行代码假定指针的长度为4字节,而这在LP64中是不正确的,此时是8字节。
//正确的方法应使用sizeof()
int32_t *p;
p = (int32_t *)malloc(sizeof(p) * ELEMENTS_NUMBER);
```
#### 【建议】特殊情况下,64位系统可以强制编译器指定对齐方式
【说明】在需要时,为保持代码兼容性,可采用指定对齐方式。使用伪指令#pragma pack (n),编译器将按照n 个字节对齐;使用伪指令#pragma pack (),取消自定义字节对齐方式。
【样例】
```c
#pragma pack(push) # 保存当前的对齐方式
#pragma pack(1) # 设置对齐方式为1字节对齐
struct test
{
......
};
#pragma pack(pop) # 恢复之前的对齐方式
```
#### 【规则】涉及多机通信的消息结构体,需要对齐统一。基于兼容性考虑,可优先采用1字节对齐;禁止使用8字节对齐和64位数据类型以避免与32位系统通信错误
【说明】板间通信消息涉及到跨板操作,除非所有软件同步升级,否则将会导致通信失败。为兼容性考虑,对已经存在的协议和结构,保持1字节对齐,并转换为网络序。对新增的通信协议,为通信效率和处理性能考虑,可以有条件采用4字节对齐。
#### 【规则】对外提供的接口数据结构应当避免出现填充,至少要求采用4字节对齐
【说明】对功能特性对外提供的API头文件,如果其中涉及到结构体定义,应当避免结构体填充,建议能够在自然对齐情况下数据不出现空洞,至少需要保证4字节对齐。
#### 【建议】应当使用成员名称访问结构体成员,禁止使用偏移方式访问
【说明】数据结构成员的偏移在32位和64位对齐情况下,其偏移值不同,不能直接计算每个成员的大小之后作为偏移;在32位系统上没有自动填充的数据结构,到了64位上有可能出现自动填充。
【示例】
```c
Struct A
{
uint32_t a;
uint32_t *p;
uint32_t b;
};
```
成员b的偏移等于sizeof(a) + sizeof(p)在32位系统上成立,在64位系统上不成立。正确的做法应当是直接通过变量名称进行索引定位:
```c
xxx.b = 123;
```
如果结构定义比较特殊,比如结构体只是消息的头部,后续还有其它字段,则可能需要重新定义此结构,使结构体内部不出现填充字段。
【示例】
```c
typedef struct {
uint32_t self; /* 其它对齐结构 */
uint32_t brother; /* 其它对齐结构 */
uint8_t processedFlag; /* 当前节点是否被处理过的标志 */
uint8_t reserve[3]; /* 为了4字节对齐的保留字段 */
} TreeNodeInfo;
typedef struct {
TreeNodeInfo nodeInfo; /* 每一个node的树信息的数据结构 */
void *userInfo; /* 每一个node的用户信息数据结构 */
} TreeNode;
```
TreeNode结构体中有两个成员结构体,下面的代码中根据第二个成员来获取第一个成员的地址,采用第二个成员首地址减去sizeof(第一个成员)计算(inUserInfo指向结构体中的userInfo字段):
```c
inTreeNodeInfo = (TreeNodeInfo *)((void *)(((char *)inUserInfo) - sizeof(TreeNodeInfo)));
```
结构体采用自然对齐,需要注意到子结构体TreeNodeInfo,在32位下其长度为12bytes,在64位下,**其长度也是12位**。结构体TreeNode在32位下成员结构体之间无填充,其长度为16bytes,但在64位下的结构体长度:sizeof(TreeNodeInfo)=12,sizeof(TreeNode)=24,即子结构体TreeNodeInfo后有4个字节的填充字段,因此在64位下通过上述方法获取前一个字段地址,就会漏算填充的4个字节,导致成员访问出错。
#### 【建议】结构体定义时,为节省存储空间,在保证可读性的前提下,尽可能实现8字节自然对齐,避免出现填充
【说明】如果结构体能够实现自然对齐,则不需要进行填充,可有效节省结构体的无效空间。在不影响可读性的前提下,建议将size_t和pointer等64位长度类型放在结构体两端定义。
【示例】
```c
// 下面的结构体,在自然对齐情况下,其长度为24bytes
struct Foo
{
int32_t a;
uint64_t l;
int32_t x;
}
// 经过适当调整,可优化到16bytes
struct Foo
{
uint64_t l;
int32_t a;
int32_t x;
}
```
### 数据类型转换及运算
#### 【规则】应当避免不同类型的数据之间的隐式类型转换,如果必要,应当采用显式类型转换,避免在32位和64位系统结果不一致
【说明】谨慎处理不同长度和精度的操作数之间进行运算,避免因默认转换出现精度和符号的丢失。64位与32位混合编程情况下,需要关注隐式类型转换的几个重要点:
1. 64位长度的变量给32长度变量赋值,低32位直接赋值,高32位被截断丢失;低32位再根据左值变量类型理解成有符号或者无符号
2. 反之,32位长度的变量给64长度变量赋值,根据源32位为有符号或者无符号进行符号扩展,目的64位数再根据变量类型理解成有符号或者无符号。
3. 有符号数与无符号数进行运算,如果不指定结果类型,系统默认按照无符号方式理解。
上述转换过程可能带来不符合原意的结果,因此需要谨慎对待,尽可能避免隐式的转换。
【示例】转换后结果看上去正确的示例
```c
int32_t t1 = -2;
int64_t t2 = 1;
int32_t t3 = t1 + t2;
printf("t3 = %d\n", t3);
// 打印结果: t3 = -1
```
t1是个32位数,t2是个64位数,在做运算之前,先把t1也扩展成64位的。加法完成之后结果是一个64位的int64_t类型,在内存中16进制存储值为:0xffffffffffffffff,在给32位int32_t类型赋值发生截断, 值为0xffffffff,再理解成有符号32位,值为-1。此结果虽然看上去正确,但发生了数据截断,可能并不符合作者的本意。
【示例】有符号向无符号转换
```c
int64_t t1 = -1;
uint32_t t2 = t1;
printf("t2=%u", t2);
// 打印结果:t2=4294967295
```
t1是个64位int64_t类型,用二进制表示为0xffffffffffffffff,如果赋值给一个32位int类型,高32位丢失,直接显示位低32位的值,二进制为0xffffffff,按照无符号方式理解,值为4294967295。
【示例】32位向64位无符号转换
```c
int32_t t1 = -1;
uint64_t t2 = t1;
printf("t2 = %lu\n", t2);
// 打印结果:t2 = 18446744073709551615
```
源32位是个有符号的负数,扩展时,应该带符号扩展,负数高位全部为f,扩展后值为0xffffffffffffffff。目的64位类型是个无符号的数,所以其值是一个很大的正数。
#### 【规则】当需要将指针作为基址,再按照字节计算偏移时,指针需要强制转换为uintptr_t或uint8_t *等单字节指针类型
【说明】如果转换为整型,采用uint32_t强转,会出现指针被截断的情况,因此需要转换为uintptr_t。也可转换为单字节宽度的指针类型,如uint8_t *、char *等,转为上述方式后,偏移均被理解为字节。void *类型实际也会按照1字节进行偏移,但为了类型明确化,建议使用前面更明确的类型代替。
【示例】
```c
// 错误
void *pPkt = (void *)((uint32_t)MSG_GET_DATA_ADDR(msgAddr) + OFFSET);
// 正确
void *pPkt = (void *)((uintptr_t)MSG_GET_DATA_ADDR(msgAddr) + OFFSET);// C
void *pPkt = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(MSG_GET_DATA_ADDR(msgAddr)) + OFFSET); // C++
```
#### 【规则】禁止指针与uint32_t之间相互赋值,包括函数参数传递
【说明】如果要定义的变量可能是不确定长度的指针,使用void *;如果要定义的变量既可以是指针,也可以是整形,使用uintptr_t。
【示例】指针与整型的转换
```c
int32_t i, *p, *q;
p = &i;
q = (int32_t *) (int32_t)&i;
// 在A32架构下,p = q;但是在A64-LP64架构下,p != q
```
为了避免这种类型冲突问题,可以用uintptr_t来表示指针类型。
#### 【规则】禁止size_t与int32_t/uint32_t之间相互赋值,包括函数参数传递
【说明】可变长度类型禁止与32长度类型强转。
【示例】
```c
int32_t length = (int32_t)strlen(str); // 错误
```
strlen返回size_t(它在LP64中是unsigned long),当赋值给一个int32_t时,截断是必然发生的。而通常,截断只会在str的长度大于2GB时才会发生,这种情况在程序中一般不会出现,容易出现问题。
#### 【规则】在64位环境下使用大数组或大for循环索引时,索引类型应当与下标边界保持一致
【说明】如果系统中使用了超大数组或超大循环,在64位环境有可能超过32位的索引范围,则需要使用可变长度类型或64位类型来定义数组下标或循环变量,避免因变量范围不足而导致不能全量遍历。
【示例】
```c
int64_t count = BIG_NUMBER;
for (unsigned int index = 0; index != count; index++)
...
```
在64位系统上,由于int64_t是64位类型,count是一个很大的数。unsigned int是32位类型 导致上面的循环永远不会终止。因此,index应该改成int64_t类型。
### 位域和字节序
#### 【规则】基于位域的变量定义需要充分考虑位域的基础类型及对齐问题,避免在不同的位宽下计算出错
【说明】位域的宽度需要结合对齐进行理解,结构体会变长;对位域的取值需要采用名称,避免采用直接计算。
#### 【规则】在64位系统上,考虑移位操作带来的长度差异,以及移位以后生成的值隐形扩展到64位的问题
【说明】位移运算时,需要检查是否可能导致溢出而回绕的情况,需要修改为32位类型避免结果不一致。
【示例】
```c
int64_t t = 1 << a; // 需考虑a的大小
``
`
在32位系统上,a的最大值是32,在64位系统上,是64。如果a是一个32位变量,其结果还要考虑到64位扩展问题。
### 第三方库和差异化功能
#### 【规则】64位系统使用到的第三方类库,都必须是支持64位的
【说明】部分库函数、第三方开源代码本身对64位的支持不定完善,需要针对所有涉及的代码进行评估,确保其可运行在64位环境下。部分库函数在64位和32位采用不同的接口实现,因此需要确保使用正确的接口。
【示例】内存映射处理,在32位系统下可使用mmap,如果映射超过2GBytes,则需要使用mmap64;但64位系统下可统一使用mmap。
#### 【规则】涉及到底层汇编的功能,需要在64位和32位系统分别独立调测,避免功能不可用
【说明】切换到64位后,寄存器个数和位宽都有变化,涉及到汇编的相关调测功能都需要进行重新的调测,并需要同时关注32位和64位下的代码有效性。
【示例】调用栈的推栈功能,在32位和64位内嵌的汇编代码需要独立编写调测。
#### 【规则】补丁功能需要同时支持32位指令和64位指令差异
【说明】由于指令长度变更,补丁机制及功能需要适配修改。
#### 【规则】64位系统使用到的工具,都必须是支持64位的
【说明】如果不可避免出现了类型不一致的情况,需要谨慎进行类型转换,确保转换后的结果满足实际应用需求。
zh-cn/contribute/OpenHarmony-hdf-coding-guide.md
0 → 100755
浏览文件 @
c05cf492
# OpenHarmony HDF驱动编程规范
## 前言
### 目的
OpenHarmony的目标是面向全场景、全连接、全智能时代,基于开源的方式,搭建一个智能终端设备操作系统的框架和平台,促进万物互联产业的繁荣发展。具有“硬件互助,资源共享”、“一次开发,多端部署”、“统一OS,弹性部署”的技术特性。
HDF(Hardware Driver Foundation)驱动框架,为开发者提供驱动框架能力,包括驱动加载、驱动服务管理和驱动消息机制。旨在构建统一的驱动架构平台,为开发者提供更精准、更高效的开发环境,力求做到一次开发,多系统部署。
因此,对基于HDF实现的OpenHarmony驱动代码需要有一定的编程规约,以满足驱动代码的“一次开发,多端部署”技术特性。本文以此为初衷,结合OpenHarmony和HDF的特点,拟定了相关编程规约,用于指导驱动代码的开发编码,提升代码的规范性及可移植性,供开发者参考。
## 编程规范
### 总则
#### 【规则】OpenHarmony的驱动程序,应当使用HDF框架提供的能力实现
【说明】HDF驱动框架提供了驱动加载、驱动服务管理和驱动消息机制,同时还提供了操作系统抽象层(OSAL, Operating System Abstract Layer)和平台抽象层(PAL, Platform Abstract Layer)来保证驱动程序的跨系统跨平台部署的特性。除此之外,HDF提供了驱动模型的抽象、公共工具、外围器件框架等能力。开发者应该基于HDF提供的这些能力开发驱动,从而保证驱动程序可以在各种形态的OpenHarmony上进行部署。
#### 【规则】开发者应当遵循此规范要求,开发能够同时满足内核态和用户态的驱动
【说明】内核态驱动与用户态驱动天然存在着差异,两种形态适用的场景也不尽相同。开发者在业务设计和开发的时候应当遵循此规范,使用HDF提供的OSAL、PAL等特性来屏蔽形态的差异,来保证开发的驱动同时满足内核态和用户态。
#### 【建议】使用HDF框架时,编译脚本应当包含drivers/framework/include目录,而不是子模块目录
【说明】drivers/framework/include目录是HDF对外暴露的头文件根目录,此目录下面按照功能划分为核心框架、OSAL和PAL等多个子模块目录。在使用对应头文件时,建议编译脚本包含到drivers/framework/include目录,这样在代码中进行引用时,可以避免重复包含,也便于区分对应子模块,达到驱动范围内的统一。
【样例】
```
gn
config("xxxx_private_config") {
include_dirs = [
"//drivers/framework/include",
"//drivers/framework/include/core", # 不建议
]
}
```
```
c
#include <core/hdf_device_desc.h>
#include <hdf_device_desc.h> // 不建议
```
### HDF核心框架
#### 【规则】应当按照驱动入口对象HdfDriverEntry中的职责定义来实现Bind、Init和Release方法,避免职责不单一引入问题
【说明】HdfDriverEntry对象是HDF驱动的入口,其中的三个方法指针均有各自的职责,开发者需按照方法职责来实现对应函数。
```
c
struct
HdfDriverEntry
g_sampleDriverEntry
=
{
.
moduleVersion
=
1
,
.
moduleName
=
"sample_driver"
,
.
Bind
=
SampleDriverBind
,
// 职责:绑定驱动对外提供的服务接口到HDF
.
Init
=
SampleDriverInit
,
// 职责:初始化驱动自身的业务
.
Release
=
SampleDriverRelease
,
// 职责:释放驱动资源,发生异常时也会调用
};
HDF_INIT
(
g_sampleDriverEntry
);
```
#### 【规则】驱动服务的结构定义,首个成员必须是IDeviceIoService类型
【说明】HDF框架内部实现约束,驱动定义的服务接口,首个成员必须是IDeviceIoService类型。
【样例】
```
c
struct
ISampleDriverService
{
struct
IDeviceIoService
ioService
;
// 首个成员必须是IDeviceIoService类型
int32_t
(
*
FunctionA
)(
void
);
// 驱动的第一个服务接口
int32_t
(
*
FunctionB
)(
uint32_t
inputCode
);
// 驱动的第二个服务接口,可以依次往下累加
};
```
【样例】
```
c
struct
ISampleDriverService
{
struct
IDeviceIoService
ioService
;
// 首个成员必须是IDeviceIoService类型
void
*
instance
;
// 也可以封装服务实例,在实例中提供服务接口
};
```
#### 【规则】在HdfDriverEntry的Bind方法中,必须完成全部驱动服务接口的绑定,禁止将服务接口未定义或定义为空
【说明】驱动定义的服务接口,均是对外暴露的,如果未定义或定义为空,可能会导致外部调用时产生异常,从而降低驱动的可靠性。
【样例】
```
c
int32_t
SampleDriverBind
(
struct
HdfDeviceObject
*
deviceObject
)
{
static
struct
ISampleDriverService
sampleDriver
=
{
.
FunctionA
=
SampleDriverServiceA
,
.
FunctionB
=
NULL
,
// 禁止定义为空
};
// 将ioService与HDF创建的设备对象进行绑定
deviceObject
->
service
=
&
sampleDriver
.
ioService
;
return
HDF_SUCCESS
;
}
```
#### 【建议】在HdfDriverEntry的Init方法中,应当调用HdfDeviceSetClass接口,对驱动的类型进行定义
【说明】驱动的类型可以用于归类当前设备的驱动程序,也可以用来查询当前设备的驱动能力。为了便于后续驱动的统一管理,建议通过HdfDeviceSetClass接口来设置当前驱动的类型。
【样例】
```
c
int32_t
SampleDriverInit
(
struct
HdfDeviceObject
*
deviceObject
)
{
// 设置驱动的类型为DISPLAY
if
(
!
HdfDeviceSetClass
(
deviceObject
,
DEVICE_CLASS_DISPLAY
))
{
HDF_LOGE
(
"HdfDeviceSetClass failed"
);
return
HDF_FAILURE
;
}
return
HDF_SUCCESS
;
}
```
### HCS配置规范
HCS(HDF Configuration Source)是HDF驱动框架的配置描述源码,内容以Key-Value为主要形式。它实现了配置代码与驱动代码解耦,便于开发者进行配置管理。
驱动配置包含两部分,HDF框架定义的驱动设备描述和驱动的私有配置信息。
**设备描述信息**
HDF框架加载驱动所需要的信息来源于HDF框架定义的驱动设备描述,因此基于HDF框架开发的驱动必须要在HDF框架定义的device_info.hcs配置文件中添加对应的设备描述。
#### 【规则】在进行驱动设备配置之前,应当明确驱动所属的硬件和部署形态,规划需要配置的目录和文件
【说明】在OpenHarmony源码的vendor目录下,按照芯片厂商、开发板、配置的目录进行规划,HDF驱动的配置位于hdf_config目录下。根据硬件规格,此hdf_config目录下存放内核态配置信息或者分别内核态和用户态的配置信息。开发者应当根据驱动所属的硬件和部署形态,确定在哪一个目录下进行配置。
【样例】
```
bash
$openharmony_src_root
/vendor/hisilicon/hispark_taurus/hdf_config
# 内核态配置文件目录,无用户态
$openharmony_src_root
/vendor/hisilicon/Hi3516DV300/hdf_config/khdf
# 内核态配置文件目录
$openharmony_src_root
/vendor/hisilicon/Hi3516DV300/hdf_config/uhdf
# 用户态配置文件目录
$openharmony_src_root
/vendor/hisilicon/Hi3516DV300/hdf_config/khdf/device_info/device_info.hcs
# 内核态驱动设备描述配置文件
$openharmony_src_root
/vendor/hisilicon/Hi3516DV300/hdf_config/khdf/lcd/lcd_config.hcs
# 内核态驱动私有配置文件
```
#### 【规则】驱动设备在配置时,应当充分使用已有的配置信息,继承已有的配置模板
【说明】在HDF框架定义的device_info.hcs配置文件中,已经配置好了host、device和deviceNode的模板,开发者在配置驱动设备时,应该充分利用已有配置信息和HCS的继承特性,减少重复的配置工作量。
【样例】
```
root {
device_info {
match_attr = "hdf_manager";
template host { // host模板
hostName = "";
priority = 100; // host启动优先级(0-200),值越大优先级越低,建议默认配100,优先级相同则不保证host的加载顺序
template device { // device模板
template deviceNode { // deviceNode模板
policy = 0; // policy字段是驱动服务发布的策略
priority = 100; // 驱动启动优先级(0-200),值越大优先级越低,建议默认配100,优先级相同则不保证device的加载顺序
preload = 0; // 驱动按需加载字段
permission = 0664; // 驱动创建设备节点权限
moduleName = "";
serviceName = "";
deviceMatchAttr = "";
}
}
}
// 继承模板的节点如果使用模板中的默认值,则节点字段可以缺省
sample_host :: host { // sample_host继承了host模板
hostName = "host0"; // host名称,host节点是用来存放某一类驱动的容器
device_sample :: device { // device_sample继承了device模板
device0 :: deviceNode { // device0继承了deviceNode模板
policy = 1; // 覆写了模板中的policy值
moduleName = "sample_driver"; // 驱动名称,该字段的值必须和驱动入口结构的moduleName值一致
serviceName = "sample_service"; // 驱动对外发布服务的名称,必须唯一
deviceMatchAttr = "sample_config"; // 驱动私有数据匹配的关键字,必须和驱动私有数据配置表中的match_attr值相等
}
}
}
}
}
```
#### 【规则】驱动模型的设计和归类应当满足业务需要和既定类型,禁止重复配置Host和Device
【说明】HDF框架将一类设备驱动放在同一个Host里面,开发者也可以将Host中的驱动功能分层独立开发和部署,支持一个驱动多个Node,HDF驱动模型如下图所示:

开发者应当将同一类的设备放在同一个Host里面,在新增设备时,检查是否已经存在同类型的Host。如果已存在Host,则将Device配置在此Host中,禁止重复配置Host。一个驱动设备应该只属于一类驱动类型,因此也禁止将同一个Device配置在不同Host当中。
#### 【规则】驱动服务必须按照业务规则设置对外发布的策略,禁止设置不必要的发布策略
【说明】驱动服务是HDF驱动设备对外提供能力的对象,由HDF框架统一管理。HDF框架定义了驱动对外发布服务的策略,是由配置文件中的policy字段来控制,policy字段的取值范围以及含义如下:
```
c
typedef
enum
{
/* 驱动不提供服务 */
SERVICE_POLICY_NONE
=
0
,
/* 驱动对内核态发布服务 */
SERVICE_POLICY_PUBLIC
=
1
,
/* 驱动对内核态和用户态都发布服务 */
SERVICE_POLICY_CAPACITY
=
2
,
/* 驱动服务不对外发布服务,但可以被订阅 */
SERVICE_POLICY_FRIENDLY
=
3
,
/* 驱动私有服务不对外发布服务,也不能被订阅 */
SERVICE_POLICY_PRIVATE
=
4
,
/* 错误的服务策略 */
SERVICE_POLICY_INVALID
}
ServicePolicy
;
```
因此,驱动服务应该按照业务规则来设置发布策略,禁止设置不必要的发布策略,如内核态驱动设置用户态的发布策略。
【样例】
```
root {
device_info {
sample_host {
sample_device {
device0 {
policy = 1; // 驱动对内核态发布服务
...
}
}
}
}
}
```
#### 【规则】驱动创建设备节点权限必须与驱动的发布规则互相匹配
【说明】在HDF框架定义的device_info.hcs配置文件中,permission为驱动创建的设备节点权限字段。该字段的取值使用Unix文件权限的八进制数字模式来表示,长度为4位,例如0644。permission字段仅在驱动服务对用户态发布服务时(即policy = 2)才会生效。
开发者应当保证驱动服务的发布策略与设备节点的权限互相匹配,否则可能会导致驱动服务无法访问或设备节点的权限被放大。
【样例】
```
root {
device_info {
sample_host {
sample_device {
device0 {
policy = 2; // 驱动对内核态和用户态都发布服务
permission = 0640; // 建议值
...
}
}
}
}
}
```
【反例】
```
root {
device_info {
sample_host {
sample_device {
device0 {
policy = 2; // 驱动对内核态和用户态都发布服务
permission = 0777; // 权限过大
...
}
}
}
}
}
```
【反例】
```
root {
device_info {
sample_host {
sample_device {
device0 {
policy = 1; // 驱动对内核态发布服务,不会创建设备节点
permission = 0640; // 冗余配置
...
}
}
}
}
}
```
#### 【规则】应当根据业务要求配置是否按需加载
【说明】在HDF框架定义的device_info.hcs配置文件中,preload为驱动按需加载字段,取值的范围见如下枚举:
```
c
typedef
enum
{
/* 系统启动时默认加载 */
DEVICE_PRELOAD_ENABLE
=
0
,
/* 当系统支持快启时,则在快启完成后再加载;如果系统不支持快启,与DEVICE_PRELOAD_ENABLE含义相同 */
DEVICE_PRELOAD_ENABLE_STEP2
,
/* 系统启动时默认不加载,当使用时HDF框架会尝试动态加载 */
DEVICE_PRELOAD_DISABLE
,
/* 无效值 */
DEVICE_PRELOAD_INVALID
}
DevicePreload
;
```
开发者应当根据驱动的业务要求,将preload字段配置为相应的值,从而HDF框架可以按照preload规则进行驱动的加载。
【样例】
```
root {
device_info {
sample_host {
sample_device {
device0 {
preload = 2; // 使用时按需加载
...
}
}
}
}
}
```
#### 【建议】当preload字段配置为默认加载时,应当根据业务要求配置按序加载的优先级
【说明】在HDF框架定义的device_info.hcs配置文件中,priority字段(取值范围为整数0到200)是用来表示Host和驱动的优先级。不同的Host内的驱动,Host的priority值越小,驱动加载优先级越高;同一个Host内驱动的priority值越小,加载优先级越高。priority字段的默认值为100,当未配置或字段值相同时,HDF框架将不保证驱动的加载顺序。开发者应当根据业务场景的要求,配置preority字段,保证各个驱动的启动顺序。
【样例】
```
root {
device_info {
sample_host0 {
priority = 100;
sample_device {
device0 {
preload = 0; // 默认加载
priority = 100; // HDF保证在device1之前加载
...
}
device1 {
preload = 0; // 默认加载
priority = 200; // HDF保证在device0之后加载
...
}
}
}
sample_host1 {
priority = 100; // 由于与sample_host0的优先级相同,HDF将不保证加载顺序
...
}
}
}
```
**驱动私有配置信息**
如果驱动有私有配置,则可以添加一个驱动的配置文件,用来填写一些驱动的默认配置信息,HDF框架在加载驱动的时候,会将对应的配置信息获取并保存在HdfDeviceObject中的property里面,通过Bind和Init传递给驱动。
#### 【规则】驱动私有配置文件应当按照器件类型或者模块进行目录划分,并放置在相应的目录下
【说明】开发者应当对驱动的私有配置文件进行合理的目录规划,禁止将私有配置文件放置在配置的根目录下。
【样例】
```
bash
$openharmony_src_root
/vendor/hisilicon/Hi3516DV300/hdf_config/khdf/sample/sample_config.hcs
# 正确,将私有配置文件放置在了sample目录下
$openharmony_src_root
/vendor/hisilicon/Hi3516DV300/hdf_config/khdf/sample_config.hcs
# 错误,将私有配置文件放置在了配置根目录下
```
#### 【规则】应当将驱动私有配置文件包含到hdf_config配置目录下的hdf.hcs文件中
【说明】hdf.hcs文件是配置信息的汇总文件,在HDF编译和运行时,将会解析此文件中的内容,加载驱动的私有配置信息到驱动的设备节点中。开发者应当保证hdf.hcs文件中包含了驱动的私有配置文件,从而保证驱动能够正确初始化。
【样例】
```
c
#include "device_info/device_info.hcs"
#include "sample/sample_config.hcs" // 包含驱动私有配置文件
root
{
module
=
"hisilicon,hi35xx_chip"
;
}
```
#### 【规则】驱动私有配置信息中的matchAttr字段值,必须与device_info.hcs中配置的deviceMatchAttr字段值一致
【说明】HDF框架会通过match_attr字段的值,来与驱动设备进行关联。如果配置错误,将导致私有配置信息无法获取。
【样例】
```
root {
sample_config {
...
match_attr = "sample_config"; // 该字段的值必须和device_info.hcs中的deviceMatchAttr值一致
}
}
```
#### 【规则】驱动私有配置信息中的字段名,使用下划线命名法
【说明】由于C/C++语言编程指导的命名规则要求,驱动的私有配置信息中的字段名,应当使用下划线命名法。这样,在实现代码中对私有配置数据结构进行定义时,可以满足命名规则,也便于代码和配置文件的统一管理。
【样例】
```
root {
sample_config {
sample_version = 1; // 使用下划线命名
sample_bus = "I2C_0";
match_attr = "sample_config";
}
}
```
### HCS宏
驱动的私有配置信息会被加载到HdfDeviceObject中的property中,因此会占用一定的内存空间,这在轻量和小型系统中带来的缺点尤为明显。为了减少私有配置信息的内存占用,HDF框架提供了HCS宏,来解析驱动的私有配置信息。
#### 【规则】在内存敏感或跨系统类型的驱动场景下,应当使用HCS宏来解析驱动的私有配置信息
【说明】开发者应当明确驱动的使用场景,如果对内存敏感或者需要跨轻量、小型和标准系统使用,应当使用HCS宏来解析驱动的私有配置信息,从而保证驱动的性能和可移植性。
【样例】
```
c
#include <utils/hcs_macro.h>
#define SAMPLE_CONFIG_NODE HCS_NODE(HCS_ROOT, sample_config)
ASSERT_EQ
(
HCS_PROP
(
SAMPLE_CONFIG_NODE
,
sampleVersion
),
1
);
ASSERT_EQ
(
HCS_PROP
(
SAMPLE_CONFIG_NODE
,
sample_bus
),
"I2C_0"
);
ASSERT_EQ
(
HCS_PROP
(
SAMPLE_CONFIG_NODE
,
match_attr
),
"sample_config"
);
```
### HDF工具
#### 【规则】在使用HdfSbuf进行数据通信时,应当明确通信的场景,并根据相应场景确定创建的HdfSbuf类型
【说明】HdfSbuf是HDF进行数据传输时的数据结构,此结构根据通信的场景区分为不同的类型,定义在hdf_sbuf.h头文件的枚举中:
```
c
enum
HdfSbufType
{
SBUF_RAW
=
0
,
/* SBUF used for communication between the user space and the kernel space */
SBUF_IPC
,
/* SBUF used for inter-process communication (IPC) */
SBUF_IPC_HW
,
/* Reserved for extension */
SBUF_TYPE_MAX
,
/* Maximum value of the SBUF type */
};
```
开发者在进行数据通信时,应当明确是跨用户态和内核态通信场景,还是用户态的进程间通信,从而创建相应的HdfSbuf。
【样例】
```
c
void
SampleDispatchBetweenUserAndKernel
()
{
int32_t
ret
;
/* 跨用户态和内核态进行通信的场景 */
struct
HdfSBuf
*
data
=
HdfSBufTypedObtain
(
SBUF_RAW
);
...
ret
=
sample
->
dispatcher
->
Dispatch
(
&
sample
->
object
,
CMD_SAMPLE_DISPATCH
,
data
,
NULL
);
...
HdfSBufRecycle
(
data
);
}
```
【样例】
```
c++
void
SampleDispatchIpc
()
{
/* 跨进程进行通信的场景 */
struct
HdfSBuf
*
data
=
HdfSBufTypedObtain
(
SBUF_IPC
);
...
int
ret
=
sample
->
dispatcher
->
Dispatch
(
sample
,
CMD_SAMPLE_DISPATCH
,
data
,
nullptr
);
...
HdfSBufRecycle
(
data
);
}
```
#### 【规则】在使用HDF的日志打印时,应当明确定义HDF_LOG_TAG日志打印的标签
【说明】HDF框架提供了一套日志打印工具hdf_log.h,开发者可以直接使用HDF的日志打印进行驱动运行日志的输出。HDF_LOG_TAG宏的作用是定义日志打印的标签,开发者必须在打印日志前进行定义。
【样例】
```
c
#include <hdf_log.h>
#define HDF_LOG_TAG sample_driver // 定义日志的标签
int32_t
SampleDriverInit
(
struct
HdfDeviceObject
*
deviceObject
)
{
HDF_LOGI
(
"sample driver is initialized"
);
// 使用HDF日志工具打印日志
return
HDF_SUCCESS
;
}
```
#### 【规则】应当对HDF框架方法的返回值进行有效判断,并使用HDF提供的错误码
【说明】HDF框架提供的方法有明确的错误码返回值,开发者在使用时应当进行判断,而不是选择忽略。对应的返回值为hdf_base.h头文件中的错误码,开发者在使用HDF或实现自定义方法时,应当统一使用HDF提供的错误码。
【样例】
```
c
int32_t
SampleDriverInit
(
struct
HdfDeviceObject
*
deviceObject
)
{
int32_t
ret
;
// 判断设备类型设置是否成功
if
(
!
HdfDeviceSetClass
(
deviceObject
,
DEVICE_CLASS_DISPLAY
))
{
HDF_LOGE
(
"HdfDeviceSetClass failed"
);
return
HDF_FAILURE
;
}
ret
=
InitDiver
();
// 自定义方法使用HDF的错误码
if
(
ret
!=
HDF_SUCCESS
)
{
HDF_LOGE
(
"init drvier is failed"
);
return
ret
;
}
return
HDF_SUCCESS
;
}
```
### OSAL框架
HDF OSAL框架屏蔽了OpenHarmony各个系统类型之间的接口差异,对外提供统一的OS接口,包括内存管理、线程、互斥体、自旋锁、信号量、定时器、文件、中断、时间、原子、固件、I/O操作模块。
#### 【规则】跨轻量、小型和标准系统类型的驱动,必须通过OSAL框架来使用操作系统接口
【说明】OSAL屏蔽了OS接口之间的差异,开发者应当基于OSAL来操作OS的接口,保证驱动能够跨系统类型运行。
【样例】
```
c
#include <osal/osal_mem.h>
#include <util/hdf_log.h>
struct
DevHandle
*
SampleInit
(
void
)
{
struct
DevHandle
*
handle
=
(
struct
DevHandle
*
)
OsalMemCalloc
(
sizeof
(
struct
DevHandle
));
if
(
handle
==
NULL
)
{
HDF_LOGE
(
"OsalMemCalloc handle failed"
);
return
NULL
;
}
return
handle
;
}
```
【样例】
```
c
#include <osal/osal_time.h>
void
SampleSleep
(
uint32_t
timeMs
)
{
OsalMSleep
(
timeMs
);
}
```
### PAL框架
HDF PAL框架对平台类驱动进行了抽象,并对外提供统一的操作接口,包括GPIO、I2C、SPI、UART、RTC、SDIO、EMMC、DSI、PWM、WATCHDOG等模块。
#### 【规则】跨轻量、小型和标准系统类型的驱动,必须通过PAL框架来使用平台驱动
【说明】PAL屏蔽了不同系统类型之间的平台驱动接口差异,开发者应当基于PAL来操作这些接口,保证驱动能够跨系统类型运行。
【样例】
```
c
#include <platform/gpio_if.h>
#include <util/hdf_log.h>
#include <osal/osal_irq.h>
#include <osal/osal_time.h>
static
uint32_t
g_irqCnt
;
/* GPIO中断服务样例函数 */
static
int32_t
SampleGpioIrqHandler
(
uint16_t
gpio
,
void
*
data
)
{
HDF_LOGE
(
"%s: irq triggered, on gpio:%u, data=%p"
,
__func__
,
gpio
,
data
);
g_irqCnt
++
;
// 如果中断服务函数触发执行,则将全局中断计数加1
return
GpioDisableIrq
(
gpio
);
}
/* GPIO样例函数 */
static
int32_t
SampleGpioIrqEdge
(
void
)
{
int32_t
ret
;
uint16_t
valRead
;
uint16_t
mode
;
uint16_t
gpio
=
83
;
// 待测试的GPIO管脚号
uint32_t
timeout
;
/* 将管脚方向设置为输出 */
ret
=
GpioSetDir
(
gpio
,
GPIO_DIR_OUT
);
...
/* 禁止该管脚中断 */
ret
=
GpioDisableIrq
(
gpio
);
...
/* 为管脚设置中断服务函数,触发模式为上升沿和下降沿共同触发 */
mode
=
OSAL_IRQF_TRIGGER_RISING
|
OSAL_IRQF_TRIGGER_FALLING
;
ret
=
GpioSetIrq
(
gpio
,
mode
,
SampleGpioIrqHandler
,
NULL
);
...
/* 使能此管脚中断 */
ret
=
GpioEnableIrq
(
gpio
);
...
g_irqCnt
=
0
;
// 清除全局计数器
timeout
=
0
;
// 等待时间清零
/* 等待此管脚中断服务函数触发,等待超时时间为1000毫秒 */
while
(
g_irqCnt
<=
0
&&
timeout
<
1000
)
{
ret
=
GpioRead
(
gpio
,
&
valRead
);
...
ret
=
GpioWrite
(
gpio
,
(
valRead
==
GPIO_VAL_LOW
)
?
GPIO_VAL_HIGH
:
GPIO_VAL_LOW
);
...
OsalMDelay
(
200
);
// 等待中断触发
timeout
+=
200
;
}
ret
=
GpioUnSetIrq
(
gpio
);
...
return
(
g_irqCnt
>
0
)
?
HDF_SUCCESS
:
HDF_FAILURE
;
}
```
zh-cn/contribute/贡献代码.md
浏览文件 @
c05cf492
...
@@ -22,6 +22,8 @@
...
@@ -22,6 +22,8 @@
-
[
Python语言编程规范
](
https://pep8.org/
)
-
[
Python语言编程规范
](
https://pep8.org/
)
-
[
C&C++语言安全编程指南
](
OpenHarmony-c-cpp-secure-coding-guide.md
)
-
[
C&C++语言安全编程指南
](
OpenHarmony-c-cpp-secure-coding-guide.md
)
-
[
Java语言安全编程指南
](
OpenHarmony-Java-secure-coding-guide.md
)
-
[
Java语言安全编程指南
](
OpenHarmony-Java-secure-coding-guide.md
)
-
[
32/64位可移植编程规范
](
OpenHarmony-64bits-coding-guide.md
)
-
[
HDF驱动编程规范
](
OpenHarmony-hdf-coding-guide.md
)
-
[
Log打印规范
](
OpenHarmony-Log-guide.md
)
-
[
Log打印规范
](
OpenHarmony-Log-guide.md
)
### 开源软件引入
### 开源软件引入
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录