protothread原理
背景
在一些规模较小的项目中,系统的复杂度还没有到需要使用rtos的必要,并且rtos本身会占用大量的代码空间。此时可以使用protothread来对工程进行类操作系统task的包装,或者实现类似调度的功能。这样日后想上操作系统时可以方便的进行适配。
protothread是什么
Protothreads是一种低开销的并发编程机制。Protothreads充当无栈的轻量级线程或协程,它使用了极小的每protothread内存:一个短整数保存执行位置,一个字节作为让步标志。
Protothreads可用于实现叫做协作式多任务的非抢占形式的并发计算,故而在一个线程yield(让步)给另一个线程的时候不会招致上下文切换。为了在一个protothread内达成yield,在线程函数内利用了达夫设备并在其switch语句内使用一个函数外部的变量。这允许在另一次函数调用时跳转(恢复)到上次的yield的地方。为了阻塞线程,这些yield要通过等待条件来守卫,使得后续的对同样这个函数的调用仍然yield,直到这个条件表达式是为真值为止。
原理
达夫设备
达夫设备利用了switch case语句的”穿透”特性,在case语句后如果没有break,则会一直执行到switch语句的结尾。以下方代码为例。
1 | #include "stdio.h" |
输入count的值为10,n为2,在执行到switch语句时条件值为10%8=2,会跳转到”case 2:”语句处。然后会因为“穿透”的特性一直执行到”while(–n > 0)”这个语句处,此时–n为1,会继续跳转到do语句处再开始执行,然后再次“穿透”到while判断,n=0,退出循环。
最后执行的结果就是打印为:10 9 8 7 6 5 4 3 2 1
protothread的实现
protothread的代码都是用宏来定义。
一个典型的用法如下:
1 | /*define and init pt struct*/ |
将宏进行展开
1 | /*PT_INIT*/ |
在第一轮进入demo_thread时,(m_pt_demo)->lc = 0,因此执行user code,然后进入yield,把PT_YIELD_FLAG置0,并把当前运行到的行号赋给(m_pt_demo)->lc,用于记录目前执行到的位置。然后退出函数,让出cpu。
当下一轮再次进入时,PT_YIELD_FLAG先置为1,然后到switch时直接跳转到了case __LINE__:处,由于PT_YIELD_FLAG为一,所以又运行user code,然后重复第一轮的操作。这就实现了一种看起来类似于“调度”的效果。
此外还有PT_WAIT_UNTIL,可以在条件不满足时让出cpu,并在条件满足后从上次执行到的位置来运行,
1 | #define PT_WAIT_UNTIL(pt, condition) \ |
还支持“子线程”、信号量之类的功能。
缺点
不能保存现场,其“线程”并没有自己的栈,其局部变量的值无法保存到下一轮运行,所以只能用全局变量来保存需要使用的值。
不存在真正的“调度”过程,代码本质上还是按照顺序来执行。(也不能说时缺点,只能说特性)
总结
protothread仅仅用了4个.文件就实现了操作系统中的一些关键功能。非常适合一些轻量级的项目来使用,可以避免移植操作系统的麻烦以及操作系统的大量资源消耗。
但由于其无栈的特性,在使用时要小心现场的保存问题。
nuttx应用的载入过程
nuttx应用的载入过程(cmake版)
以apps/examples/helloxx这个应用为例
其应用入口为apps/examples/helloxx/helloxx_main.cxx中的extern "C" int main(int argc, FAR char *argv[])
函数
在该目录下CMakeLists.txt中将其添加到nsh的命令中
1 | nuttx_add_application( |
在nuttx/cmake/nuttx_add_application.cmake中将创建一个名为apps_helloxx的library
1 | set(TARGET "apps_${NAME}") |
再通过nuttx_add_library_internal将nuttx相关的头文件以及依赖传进来。
修改helloxx_main.cxx中main函数的名字改为helloxx_main
1 | list(GET SRCS 0 MAIN_SRC) |
将应用的主函数名字、应用名字、PRIORITY、STACKSIZE参数则传入对应的properties中
1 | set_target_properties(${TARGET} PROPERTIES APP_MAIN ${NAME}_main) |
安卓平板写markdown并同步github
背景
买了台荣耀的v8pro想平时用来写写画画,顺便写写文档博客之类的,就想折腾下看看怎么在安卓平板上搞一套环境。反正有这篇文档的时候环境肯定是能用了。大致讲一下配置把
环境搭建
markdown的编辑器用的软件是markor,对平板支持不错。
git环境的搭建用的是termux,靠这个就可以在平板上用命令行了,
先更新源
apt update && apt upgrade
再进行git安装
pkg install git
安装完成后,查看下版本信息,看看是否安装成功
git --version
输入如下指令,然后允许获取存储权限,平板的本地存储就会挂载到”/sdcard/”这个路径下面
termux-setup-storage
生存ssh密钥,把公钥同步到github上
ssh-keygen
git环境就配好了。
- 拉取好github上的仓库后,用markor进行编辑操作,再通过命令行推送到远程就ok了。
待解决和尝试
- 还没尝试在平板上搭建hexo的环境,所以博客的部署还需要通过ssh到服务器上来操作。要是能搭好环境,那就很爽了。
用samba搭建私有云
用samba搭建私有云
1、在ubuntu上安装samba
sudo apt-get install samba samba-common
2、修改samba配置文件
sudo vim /etc/samba/smb.conf
在最下方添加
[share] comment = share folder browseable = yes path = /home/abc/data #需要共享的路径 create mask = 0755 directory mask = 0755 valid users = abc #用户名称 force user = abc force group = abc public = yes available = yes writable = yes
如果要从外网访问,则要修改端口,(运营商一般默认关闭445端口)
在[global]标签下增加下面的语句,端口号为你想设置的端口smb ports = 端口号
3、给共享目录添加权限
sudo chmod 777 /home/abc/data
4、添加用户并设置访问密码
sudo smbpasswd -a abc
5、重启samba服务器
sudo service smbd restart
如果没有修改端口号,则已经可以在局域网内访问了
在windows端打开运行 win+R
输入双反斜杠加IP或者域名就可以访问\\192.168.1.11
6、windows端口转发设置
关闭占用445端口的应用sc config LanmanServer start= disabled
net stop LanmanServer
开启ip helper服务sc config iphlpsvc start= auto
设置转发netsh interface portproxy add v4tov6 listenport=445 listenaddress=127.0.0.1 connectport=替换为端口号 connectaddress=替换为动态域名
因为我的域名是ipv6的解析,所以用v4tov6
重启电脑后生效。
7、windows下通过访问共享文件夹
注意,此时进行了端口转发,所以不是使用smb服务器的地址!\\127.0.0.1
在输入用户名和访问密码后即可登录。
8、ios端访问
用系统自带的服务器连接工具不能指定端口,需要借助第三方软件ES文件浏览器,在新建中选择SMB,然后输入域名、端口、用户名密码就可以访问。
stm32外部晶振故障检测
背景
在可靠性需求中,需要考虑在外部晶振失效的情况下,能够将系统的晶振源切换为内部的振荡器,让系统至少能够维持能够发出晶振失效告警的状态,如果能让系统保持正常系统正常运行那是最好。
方法
在stm32中,有Clock security system (CSS)的机制,当外部晶振出现问题,比如晶振停振、短时间内出现大的频率偏差就会触发(fae说的,没有找到具体的触发机制文档),当触发时会系统时钟源会从HSE或者HSE为源的PLL切换到HSI,并进入NMI.(non-maskable interrupt)中断。
当在cubemx中配置Enable CSS后,会在NMI中断中跳转到HAL_RCC_NMI_IRQHandler函数中,可以在该函数中进行时钟的重新配置,将PLL Source Mux的源调整为HSI,并将M\N\P的系数进行调整,使系统时钟与之前保持一致。之后系统会以内部时钟的形式继续运行。经过实测can通讯串口通讯不会受到影响,切换会花费多少时间还需测试。
切换时间测量
测量切换时间可以使用stm32的MCO(microcontroller clock output)功能,将PLL时钟输出到引脚上,测量该引脚时钟消失的时间就可以得到切换时间。
实际操作时,示波器没法在时钟消失的时候进行触发(频率触发没搞明白用法),配置一个GPIO为低电平,在NMI的中断函数中改为高电平,通过上升沿触发的方式来触发示波器。
从图中可以看出切换时间约为128us的时间。
C++提取字符串中的数字(strtok_s的使用)
之前遇到需要将表示时间的字符串,例如“1995-10-08”,将其中的年月日提取出来用int型存储。C++(11)下没有直接转换标准时间格式的函数。直接用if语句提取来进行判断并截取十分的麻烦费时。
可以用strtok_s(linux下为strtok_r)函数进行处理,其形式为:
1 | char *strtok_s( char *strToken, const char *strDelimit, char **buf); |
其返回值是被切割的第一个子字符串。strToken为被切割的字符串,strDelimit包含分隔字符的字符串,buf为切割后剩余字符串。字符串切割完后会将原字符串中的分隔字符替换为\0。
使用方法如下:
1 | typedef struct Date |
需注意的是strDelimit为字符串的话并不是以整个字符串作为分隔符,而是其中每个字符单独作为分隔符。
针对在线笔试的话直接用函数
1 | scanf("%d-%d-%d",&year,&month,&day); |
就完事了,不用麻烦的去操作字符串,太浪费时间。
栈的顺序存储结构
栈的顺序存储
参考《大话数据结构》——程杰
栈又称为后进先出(Last In First Out)的线性表,简称LIFO结构。
进栈出栈变化形式
最先进栈的元素不一定最后出栈。
栈对线性表的插入和删除的位置进行了限制,并没有对元素进出的时间进行限制,在不是所有元素都进栈的情况下,事先进栈的元素也可以出栈,只要保证时栈顶元素出栈就可以。
顺序栈代码实现
当栈存又一个元素时,top等于0,因此通常把空栈的判定条件定位top等于-1。
1 | #define MAX_SIZE 10 |
两栈共享空间
让两个栈的栈底分别位于数组的始端与末端。两个栈增加元素,就是两端点向中间延伸。
1 | /*两栈共享空间初始化*/ |
CVTE2019嵌入式软件实习笔试题
第一次参加招聘笔试,试试水。我把部分题目记了下来,可供参考。
1 | 1、具有C语言功能,又称过滤器的是() |
const修饰的成员函数只能范围const修饰的成员?
6、需要经过三次握手的指令
7、
class Base1
{
public:
Base1(int32_t value):value_(value){}
virtual void PrintValue(){std::cout<<value_;}
private:int32_t value_;
};
class Base2
{
public:
virtual void PrintHello(){std::cout<<"hello";}
};
class Droid:public Base1,public Base2
{
public:
Droid(int32_t value):Base1(value){}
void printValue()override{std::cout<<2;}
void printHello()override{std:cout<<"world";}
};
int main()
{
std::cout<<sizeof(Base1)<<" "<<sizeof(Base2)<<" "<<sizeof(Droid);
}
分析代码,写出程序运行结果。