目录
该博客仅用来记录学习和工作中遇到的问题和解决方案,另外还包括一些工具便捷使用的方案,仅供参考和学习
Crash case
1. 栈满了(stack over)
a. 增加Task的栈大小
b. 当一个函数被注册在B Task中时(该Task的栈不能修改),可以在该函数中通过消息队列,将数据发送到A Task中(可自定义的Task)处理
2. 同一块buf释放两次
a. 内存分配注意事项:初始化后判空;使用后释放;释放后置空
char *pm = (char *) malloc(size);
if (pm == NULL) 内存分配失败 return;
使用pm
free(pm);
pm = NULL;
3. 当Crash 时,但是因为log 会丢失,不能确切定位到Crash地方
a.这种情况很特殊,有时候会有完整的Crash报告,可以根据相关的工具解析出来大概Crash的原因;
b.如果不好定位地方,可以先==拿掉一些可疑函数==,从而粗略确定Crash位置,然后可以在可疑的
地方==提前return==(即不让他跑一段),来确定精细位置;总的来说就是精确定位Crash位
置,==将Crash慢慢的夹出来==。
Problem solutions
1. 一个独立的模块想要调其他模块的函数接口
在原方案中,模块B需要调用模块A中的read and write 函数,因此需要包含模块A的头文件
解决方案:为了让模块B更加独立,可以在模块B中建立read 和 write 函数指针,并创建注册函数;
在模块A中调用模块B的注册函数,将模块A中的函数可以在模块B中使用,而保证编译的时候不依赖与模块A。
实现如下。
优点:减少模块之间的耦合
模块B:
typedef void (*read_f)(char* buf, int len);
typedef void (*write_f)(char* buf, int len);
read_f pread = NULL;
write_f pwrite = NULL;
void register_read_fun(void* pfun)
{
pread = (read_f)pfun;
}
void register_write_fun(void* pfun)
{
pwrite = (write_f)pfun;
}
void xxxB_task()
{
if (xxx){
pread(buf, len);
}else (xxx){
pwrite(buf, len);
}
}
模块A:可以使用include模块B的头文件来调用注册函数,或者使用extern
extern void register_read_fun(void* pfun)
extern void register_write_fun(void* pfun)
void read(char* buf, int len)
{
//读操作
}
void write(char* buf, int len)
{
//写操作
}
void xxxA_task()
{
register_read_fun(read);
register_write_fun(write);
while(1){
//do some thing
}
}
2. porting layer:
Q:一个烧录程序的功能,可以通过IIC\SPI\UART三种通信方式,未来还可能增加新的通信方式,
为了降低开发成本怎么设计。
A:烧录程序这一段的flow是相同的,仅通信方式不一样,通信中涉及到read,write,
setting等函数,我们可以使用一个函数指针结构体来实现上述功能。如果是C++就更好做了,
先建一个base类,其中有read,write,setting的虚函数,然后通过继承分别去实现不同的通信方式。
优点:扩展性好
通过结构体实现上述方案:
typedef void (*read_f)(char* buf, int len);
typedef void (*write_f)(char* buf, int len);
typedef void (*setting_f)(void* set);
typedef struct {
read_f pread;
write_f pwrite;
setting_f psetting;
} Com;
Com c;
void main_task_init()
{
#ifdef IIC
c = iic函数接口
#enif
#ifdef SPI
c = spi函数接口
#enif
#ifdef UART
c = uart函数接口
#enif
}
void main()
{
main_task_init();
c.psetting(buf, len);
.....
c.pread(buf, len);
....
c.pwrite(buf, len);
}
如果需要新加通信方式CAN
只需要新建一个文件,实现结构体中的函数,然后在main_task_init加上函数的注册即可
3. Task没有处理消息
1.可能是消息队列爆掉了
2.可能卡在处理某一条消息里面了,退不出来,所以下一条消息也没办法处理
4. 定时器
1.使用POSIX函数timer_create创建定时器(可移植性好)
2.使用epoll,及事件驱动方式,通过timerfd_create创建(适合配合文件系统使用)
5. 一般嵌入式软件架构(仅参考)
一般来说,分为四个层,分别是app层,service 层, kernel 层和drive驱动,
如下是一个host(PC或者手机),app层,service层设计的例子:
1. 从上到下host->app->service:host 发送送数据->app, app 通过中断接
收解析数据并发送->main_task(或者其他task)中,其中根据不同的
消息ID,处理不同的事情,如果需要将数据传输到service层的可以直接
调用service层的handle函数,service层可以保存数据,或者加载数据提供给kernel。
2.从下层到上层service->app->host: service层有一个注册函数,
可以将app的一个函数当作一个用户来注册(因此service还可以支持多用户,
需要一个source id来区分用户),在service处理传递kernel上来的数据,
调用用户的回调函数,回调函数中,根据不同的消息ID,处理不同功能的数据,
然后将结果通过uart的方式传输给host。
3.一般的消息格式typedef {int msg_id; int msg_len; char* msg_data} msg;
6. Android NDK Crash分析(一般是C++写的守护进程,用于对接硬件和HIDL)
一般是分析Crash的Callstack 或者是Tombstone;
分析流程和使用方法可参考:https://zhuanlan.zhihu.com/p/52270464
7. 嵌入式中常见的异常情况
1.assert : 断言, assert(0)
2.NMI Fault : 非屏蔽中断,可以先关闭中断,然后调用while(1); 进入死循环,等3-4分钟会触发该Crash
3.HARD Fault : 硬件故障,assert(0)也属于这一类
4.MEM Fault : 内存访问错误, char* p = NULL; *p=0; 可触发
5.USAGE Fault : 使用错误,比如除零;当时在这卡了好久,没有实现这种Crash;
如果未出现这种Crash可以考虑以下两种情况:
a.被系统优化了,我们可以在变量前面加volatile 修饰
b.系统没有打开除0的异常处理,需要在系统初始化的时候去设置
Good tool solutions
1. 在win10上学习Linux
需求来源:如果你使用的是win10系统,但是想进行Liunx开发或者学习。我们可以选择安装虚拟机,
或者安装双系统(win+Linux),相比与后者,前者肯定是较优方案;但是在win10,
可以在Microsoft stroe 下载 Ubuntu 24.04 LTS,目前我认为是最优方案。
如下是安装步骤以便使用:
1.去Microsoft stroe 下载 Ubuntu 24.04 LTS, 并根据他的安装提示(需要设置一些东西),
然后重启,之后就可以使用linux的黑窗命令行了,是不是很简单;
2.安装Vscode, 安装后下载WLS插件(点击左下角的><符号可选择连接), 这样我们就可以以Vscode为编辑器,
进行后续工作了,非常方便,既可以编辑又可以输入命令,还包含了许多插件。
重要的是就不用面对黑框Terminal了,真好。
3.在开发中遇到什么工具没有,就下载,比如make, gdb, g++, gcc等,下载命令,敲一下他会有提示和建议
4.怎么优雅便捷的使用就需要自己慢慢探索了。