win10环境使用LEX实现词法分析

一、安装配置Parser Generator1.安装Parser Generator->百度云下载地址2.安装vc6.0->百度云下载地址3.安装完后,先打开Parser Generator,Project->LibBuilder,双击Visual c++(32-bit),出现如下弹窗:4.options里的设置如下: - Complier Version: Version6- Unicode: True- Treat wchar_t as Built-in Type: Fasle- Complier Bin Directory: vc安装目录..\MICROSOFT VISUAL STUDIO\VC98\BIN- Complier Bin Directory(2): vc安装目录..\MICROSOFT VISUAL STUDIO\COMMON\MSDEV98\BIN- Complier Include Directory: vc安装目录..\MICROSOFT VISUAL STUDIO\VC98\INCLUDE- Complier Include Directory(2): vc安装目录..\MICROSOFT VISUAL STUDIO\VC98\MFC\INCLUDE- Complier Library Directory: vc安装目录..\MICROSOFT VISUAL STUDIO\VC98\LIB- Complier Library Directory(2): vc安装目录..\MICROSOFT VISUAL STUDIO\VC98\MFC\LIB5.新建项目: Project->ParserWizard,一路下一步,按默认即可6.在mylexer.l里写如下示例代码: %{ #include <stdio.h> #include <stdlib.h>%}/*difine*/IF ifTHEN thenELSE elseWHILE whileBEGIN beginEND endINT [0-9]+FLOAT {INT}[\.][0-9]+REAL {FLOAT}([e|E][+|-][0-9]+)ID [a-zA-Z]+[0-9a-zA-Z]*LE [<][=]GE [>][=]NE [<][>]EQ [=]LT [<]GT [>]IS [:][=]PL [\+]MI [-]MU [\*]DI [\/]%%{IF} {printf("(IF, )\n");}{THEN} {printf("(THEN, )\n");}{ELSE} {printf("(ELSE, )\n");}{WHILE} {printf("(WHILE, )\n");}{BEGIN} {printf("(BEGIN, )\n");}{END} {printf("(END, )\n");}{ID} {printf("(ID, '%s')\n",yytext);}{REAL} {printf("(REAL, %g)\n",atof(yytext));}{FLOAT} {printf("(REAL, %s)\n",yytext);} {INT} {printf("(INT, %s)\n",yytext);}{LE} {printf("(LE, )\n");}{GE} {printf("(GE, )\n");}{NE} {printf("(NE, )\n");}{EQ} {printf("(GQ, )\n");}{LT} {printf("(LT, )\n");}{GT} {printf("(GT, )\n");}{IS} {printf("(IS, )\n");}{PL} {printf("(PL, )\n");}{MI} {printf("(MI, )\n");}{MU} {printf("(MU, )\n");}{DI} {printf("(DI, )\n");}[ \t\n]+ ; /* ignore whitespace */[!-~]+ { printf("Invalid input: %s\n",yytext); } %%void main(){ yyin=fopen("E:\\example.txt","r"); yylex(); fclose(yyin); system("PAUSE");}//必须要包含的函数int yywrap(){ return 1;}然后Project->Complie File, 如果为灰色,则先Rebuild All,稍等片刻,再Complie File编译成功页面如下: ...

November 5, 2019 · 1 min · jiezi

逻辑回归原理小结

逻辑回归是一个分类算法,它可以处理二元分类以及多元分类。虽然它名字里面有“回归”两个字,却不是一个回归算法。那为什么有“回归”这个误导性的词呢?个人认为,虽然逻辑回归是分类模型,但是它的原理里面却残留着回归模型的影子,本文对逻辑回归原理做一个总结。 从线性回归到逻辑回归 我们知道,线性回归的模型是求出输出特征向量Y和输入样本矩阵X之间的线性关系系数,满足。此时我们的Y是连续的,所以是回归模型。如果我们想要Y是离散的话,怎么办呢?一个可以想到的办法是,我们对于这个Y再做一次函数转换,变为。如果我们令的值在某个实数区间的时候是类别A,在另一个实数区间的时候是类别B,以此类推,就得到了一个分类模型。如果结果的类别只有两种,那么就是一个二元分类模型了。逻辑回归的出发点就是从这来的。下面我们开始引入二元逻辑回归。 二元逻辑回归的模型 上一节我们提到对线性回归的结果做一个在函数g上的转换,可以变化为逻辑回归。这个函数g在逻辑回归中我们一般取为sigmoid函数,形式如下: 它有一个非常好的性质,即当z趋于正无穷时,趋于1,而当z趋于负无穷时,趋于0,这非常适合于我们的分类概率模型。另外,它还有一个很好的导数性质: 这个通过函数对求导很容易得到,后面我们会用到这个式子。 如果我们令中的z为:,这样就得到了二元逻辑回归模型的一般形式: 其中x为样本输入,为模型输出,可以理解为某一分类的概率大小。而为分类模型的要求出的模型参数。对于模型输出,我们让它和我们的二元样本输出y(假设为0和1)有这样的对应关系,如果 ,即, 则y为1。如果,即, 则y为0。y=0.5是临界情况,此时为, 从逻辑回归模型本身无法确定分类。 的值越小,而分类为0的的概率越高,反之,值越大的话分类为1的的概率越高。如果靠近临界点,则分类准确率会下降。 此处我们也可以将模型写成矩阵模式: 其中为模型输出,为 mx1的维度。X为样本特征矩阵,为mxn的维度。为分类的模型系数,为nx1的向量。 理解了二元分类回归的模型,接着我们就要看模型的损失函数了,我们的目标是极小化损失函数来得到对应的模型系数。 二元逻辑回归的损失函数 回顾下线性回归的损失函数,由于线性回归是连续的,所以可以使用模型误差的的平方和来定义损失函数。但是逻辑回归不是连续的,自然线性回归损失函数定义的经验就用不上了。不过我们可以用最大似然法来推导出我们的损失函数。 我们知道,按照第二节二元逻辑回归的定义,假设我们的样本输出是0或者1两类。那么我们有: 把这两个式子写成一个式子,就是: 其中y的取值只能是0或者1。 得到了y的概率分布函数表达式,我们就可以用似然函数最大化来求解我们需要的模型系数。 为了方便求解,这里我们用对数似然函数最大化,对数似然函数取反即为我们的损失函数)。其中: 似然函数的代数表达式为: 其中m为样本的个数。 对似然函数对数化取反的表达式,即损失函数表达式为: 损失函数用矩阵法表达更加简洁: 其中E为全1向量。 二元逻辑回归的损失函数的优化方法 对于二元逻辑回归的损失函数极小化,有比较多的方法,最常见的有梯度下降法,坐标轴下降法,等牛顿法等。这里推导出梯度下降法中每次迭代的公式。由于代数法推导比较的繁琐,我习惯于用矩阵法来做损失函数的优化过程,这里给出矩阵法推导二元逻辑回归梯度的过程。 对于,我们用对向量求导可得: 这一步我们用到了向量求导的链式法则,和下面三个基础求导公式的矩阵形式: 为函数 ...

November 4, 2019 · 1 min · jiezi

PHP7源码分析奇妙的jsonencode

baiyan json_encode()的奇怪输出最近在工作中碰到了一个现象:对于一个以数字为索引的PHP数组,在数组索引下标分别为连续和不连续的情况下,我们在分别对其进行json_encode()之后,得到了两种不一样的输出结果。看下面一段代码: <?php$arr = [4, 5, 6];echo json_encode($arr);unset($arr[1]);echo PHP_EOL;echo json_encode($arr);我们首先初始化一个数组,然后将其索引位置为1的元素去掉。由于PHP在unset()之后,并不会对数组的数字索引进行重新组织,导致该索引数组的下标不再连续。运行这段代码,输出结果如下: [4,5,6]{"0":4,"2":6}我们可以看到,在数组的数字索引连续的情况下,输出了一个json数组;而在数字索引不连续的情况下,输出了一个json对象,而并不是我们预期json数组。那么,在PHP源码层面中是如何实现的?PHP底层如何判断数组是否连续?这种处理方式是否合理呢? json_encode()源码分析接下来我们通过gdb来看一下PHP源码层面中,json_encode()对数组类型的编码处理。首先找到json_encode()函数的源码实现: static PHP_FUNCTION(json_encode){ ...... // 初始化encoder结构体(在具体encode阶段才会用到) php_json_encode_init(&encoder); // 执行json_encode()逻辑 php_json_encode_zval(&buf, parameter, (int)options, &encoder); ......}这个php_json_encode_zval()函数是json_encode()的核心实现,我们启动gdb并在这里打一个断点:运行上面这段代码,我们发现已经执行到了断点处。使用n命令继续往下执行:首先进入了一个switch条件选择,它会判断PHP变量的类型,然后执行相应的case。我们这里是数组类型,用宏IS_ARRAY表示。完整的php_json_encode_zval()方法代码如下: int php_json_encode_zval(smart_str *buf, zval *val, int options, php_json_encoder *encoder) /* {{{ */{again: switch (Z_TYPE_P(val)) { case IS_NULL: smart_str_appendl(buf, "null", 4); break; case IS_TRUE: smart_str_appendl(buf, "true", 4); break; case IS_FALSE: smart_str_appendl(buf, "false", 5); break; case IS_LONG: smart_str_append_long(buf, Z_LVAL_P(val)); break; case IS_DOUBLE: if (php_json_is_valid_double(Z_DVAL_P(val))) { php_json_encode_double(buf, Z_DVAL_P(val), options); } else { encoder->error_code = PHP_JSON_ERROR_INF_OR_NAN; smart_str_appendc(buf, '0'); } break; case IS_STRING: return php_json_escape_string(buf, Z_STRVAL_P(val), Z_STRLEN_P(val), options, encoder); case IS_OBJECT: if (instanceof_function(Z_OBJCE_P(val), php_json_serializable_ce)) { return php_json_encode_serializable_object(buf, val, options, encoder); } /* fallthrough -- Non-serializable object */ case IS_ARRAY: { /* Avoid modifications (and potential freeing) of the array through a reference when a * jsonSerialize() method is invoked. */ zval zv; int res; ZVAL_COPY(&zv, val); res = php_json_encode_array(buf, &zv, options, encoder); zval_ptr_dtor_nogc(&zv); return res; } case IS_REFERENCE: val = Z_REFVAL_P(val); goto again; default: encoder->error_code = PHP_JSON_ERROR_UNSUPPORTED_TYPE; if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) { smart_str_appendl(buf, "null", 4); } return FAILURE; } return SUCCESS;}判断传入参数的数据类型我们现在关注IS_ARRAY这个case。他首先定义了一个zval,然后将我们传入的PHP参数变量拷贝到新的zval中,避免修改我们原本传入的zval。接着,正如我们上图gdb中所示,php_json_encode_array()这个核心方法被调用,看方法名,我们就知道应该是专门处理参数为数组的情况,我们s进去,这里应该就是具体的判断逻辑了:进入到php_json_encode_array()函数中,这里又判断了一次zval的类型是否为IS_ARRAY。为什么要这样做呢?这里是因为当变量为对象的时候,即IS_OBJECT,也会调用这个方法来进行encode处理。然后进入到这句最重要的判断逻辑: ...

November 3, 2019 · 2 min · jiezi

Dex文件解析

Dex文件结构文件头typedef struct { u1 magic[MAGIC_LENGTH]; /* includes version number */ u4 checksum; /* adler32 校验剩余长度的文件 */ u1 signature[kSHA1DigestLen]; /* SHA-1 文件签名 */ u4 fileSize; /* length of entire file */ u4 headerSize; /* offset to start of next section */ u4 endianTag; u4 linkSize; u4 linkOff; u4 mapOff; u4 stringIdsSize; //字符串表大小 偏移 u4 stringIdsOff; u4 typeIdsSize; //类型表 大小偏移 u4 typeIdsOff; u4 protoIdsSize; //原型表 大小 偏移 u4 protoIdsOff; u4 fieldIdsSize; //字段表 大小 偏移 u4 fieldIdsOff; u4 methodIdsSize; //函数表 大小 偏移 u4 methodIdsOff; u4 classDefsSize; //类定义表 大小 偏移 u4 classDefsOff; u4 dataSize; //数据段 大小 偏移 u4 dataOff;}DexHeader;DexHeader由于是定长结构 直接格式化就好 ...

November 2, 2019 · 3 min · jiezi

第12课-注释符号

注释规则·编译器在编译过程中使用空格替换整个注释·字符串字面量中的//和/.../不代表注释符号·/.../型注释不能被嵌套加上空格,我们在写代码的时候加上空格是非常有必要的y = x / *p;

October 22, 2019 · 1 min · jiezi

qDebug-stdcout-printf性能表现

Qt君最近感觉qDebug相对于printf打印感觉有些慢,但又没有证据,于是闲着就写下qDebug,std::cout,printf的性能表现咯。注:测试数据仅供参考。0x00 测试环境环境参数CPUi5-8250U内存8G操作系统Windows@64位Qt版本Qt 5.12.1编译器MSVC2017@64位0x01 数据呈现 通过使用qDebug,std::cout,printf在1秒内打印的字符串数据。 分别各测试10次后取平均值,详细数据在文末。 debug版本(次/秒)release版本(次/秒)qDebug3831760923std::cout382890372696printf432606386663图表化显示 0x02 数据分析性能表现:printf > std::cout > qDebug;qDebug()相对于std::cout和printf差距过大(6~10倍);std::cout与printf数据基本一致;std::cout与printf的debug与release差距不大,甚至有debug比release快的现象(可能受实验环境影响)。0x03 结论qDebug比std::cout和printf慢,高频调用有可能影响系统时延;性能均衡推荐选用std::count;追求性能选用printf。0x04 测试程序#include <QDebug>#include <QElapsedTimer>#include <iostream>/* 注:单独打开某个宏测试 *///#define TEST1//#define TEST2//#define TEST3int main(int argc, char *argv[]){#ifdef TEST1 { QElapsedTimer t; qint64 it = 0; t.start(); while (t.elapsed() < 1000) { qDebug() << "Test1"; it++; } qDebug() << "Test1: " << it; }#endif#ifdef TEST2 { QElapsedTimer t; qint64 it = 0; t.start(); while (t.elapsed() < 1000) { std::cout << "Test2" << std::endl; it++; } std::cout << "Test2: " << it; }#endif#ifdef TEST3 { QElapsedTimer t; qint64 it = 0; t.start(); while (t.elapsed() < 1000) { printf("Test3\n"); it++; } printf("Test3: %lld\n", it); }#endif return 0}0x05 测试数据(各10次)debug版本qDebug: 38310 38452 39416 38420 38962 38385 39293 38814 34178 38946std::cout: 389512 397234 378168 367970 366371 364401 405547 405992 365863 387846printf: 468310 423937 480598 385025 490155 489473 373419 397995 445099 372054release版本qDebug: 60779 60710 59450 59685 63298 61044 59788 61167 61822 61495std::cout: 352541 358754 377001 380487 397576 362145 333757 413027 416352 335320printf: 468310 329729 333142 320171 333825 330411 471041 473771 468310 337921

October 17, 2019 · 1 min · jiezi

TencentOS-tiny-超详细的TencentOS-tiny移植到STM32F103全教程

移植前的准备工作1. 获取STM32的裸机工程模板STM32的裸机工程模板直接使用野火STM32开发板配套的固件库例程即可。可以从我github上获取https://github.com/jiejieTop/TencentOS-Demo 下载TencentOS tiny 源码TencentOS tiny的源码可从TencentOS tiny GitHub仓库地址https://github.com/Tencent/TencentOS-tiny下载,如果GitHub下载慢,也可以通过腾讯工蜂开源仓下载,地址:https://git.code.tencent.com/Tencent_Open_Source/TencentOS-tiny ,大家在移植时并不需要把整个TencentOS tiny 源码放进工程文件中,否则工程的代码量太大。杰杰将在下文讲解如何将TencentOS tiny移植到工程中去,以及如何把TencentOS tiny源码中的核心部分单独提取出来,方便以后在不同的平台上移植。目前使用的是TencentOS tiny最新版本,由于TencentOS tiny在不断更新,如果以后TencentOS tiny更新到更高的版本,则以最新的版本为准。 TencentOS tiny源码核心文件夹分析打开TencentOS tiny源码文件,可以看见里面有12个文件夹,下面先来了解主要文件夹及其子文件夹的作用,然后将TencentOS tiny源码的核心文件提取出来,添加到工程根目录下的文件夹中,因为工程只需要有用的源码文件,而不是全部的TencentOS tiny源码,所以可以避免工程过于庞大。 一级目录二 / 三级目录说明(杰杰)archarmTencentOS tiny适配的IP核架构(含M核中断、调度、tick相关代码),对我们的移植很重要archrisc-vTencentOS tiny适配的risc-v架构boardTencentOS_tiny_EVB_MXTencentOS tiny 定制开发板demo,包含AT适配框架、MQTT协议、安全组件等componentconnectivity / loraWANloRaWAN协议栈实现源码及适配层 connectivity / Eclipse-Paho-MQTTMQTT协议栈实现源码及适配层 connectivity / TencentCloud_SDK腾讯云C-SDK实现源码及适配层 fs文件系统实现源码 securitymbedtls 安全协议源码 utils包含json相关源码devices TencentOS tiny适配的一些外设驱动(如串口wifi gprs 驱动等)doc TencentOS tiny相关技术文档及开发指南(建议多看这部分)examples TencentOS tiny提供的功能示例kernelcoreTencentOS tiny内核源码(这部分是最重要的) halTencentOS tiny驱动抽象层 pmTencentOS tiny低功耗模块源码netatTencentOS tiny为串口类通信模组提供的AT框架实现层 lora_module_wrapperTencentOS tiny为串口类LoraWAN模块提供的移植框架 lwipLwip协议实现源码及适配层 sal_module_wrapperTencentOS tiny为串口类网络模块(wifi gprs)提供的socket移植框架 tencent_firmware_module_wrapperTencentOS tiny提供的腾讯定制模组移植框架osalcmsis_osTencentOS tiny提供的cmsis os 适配platformhalTencentOS tiny适配的部分芯片的驱动实现源码 vendor_bsp芯片厂家提供的原厂bsp固件库,如STM32的HAL库test 存放TencentOS tiny提供的一些测试代码,含内核及上层模块示例及测试代码tools 存放TencentOS tiny提供的工具,小程序,配置工具等简单提一下我们的重点文件夹: ...

October 17, 2019 · 1 min · jiezi

TencentOS-tiny又有一个操作系统开源

新闻2019年9月18日,腾讯宣布将开源 自主研发的轻量级物联网实时操作系统TencentOS tiny。相比市场上其它系统,腾讯TencentOS tiny在资源占用、设备成本、功耗管理以及安全稳定等层面极具竞争力。该系统的开源可大幅降低物联网应用开发成本,提升开发效率,同时支持一键上云,对接云端海量资源。 源码已在github上开源:https://github.com/Tencent/TencentOS-tiny正题很荣幸,能亲眼见证TencentOS tiny的开源,也很荣幸能在一个多月前内测使用过它~ 不得不说,TencentOS tiny的内核确实是非常非常小巧,最少资源占用为RAM 0.6KB,ROM 1.8 KB。这是他们团队自主研发的RTOS,源码非常简单易懂,源码作者以非常清晰的逻辑实现了整个内核(还是非常佩服戴大神的,膜拜一下)。 了解一下TencentOS tiny的框架TencentOS tiny主要由一个轻量级RTOS内核+多个物联网组件构成,,从下到上主要包括: CPU库 :TencentOS tiny支持的CPU IP核架构,当前主要支持ARM Cortex M0/3/4/7,还有现在很火的RISC-V,当然,腾讯物联网团队肯定也会支持更多种类更多IP核与开发板。 驱动管理层 :包括BSP板级支持包,这些东西主要由MCU芯片厂家开发与维护,、HAL硬件抽象、Drivers设备驱动,这部分对于纯粹嵌入式开发者来说还是很重要的,肯定会越来越完善的! 内核 :TencentOS tiny实时内核包括任务管理、实时调度、时间管理、中断管理、内存管理、异常处理、软件定时器、链表、消息队列、信号量、互斥锁、事件标志等模块,接下来我也将写一系列TencentOS tiny内核源码分析的文章,敬请期待吧! IoT协议栈 :TencentOS tiny提供lwip、AT Adapter、SAL层,支持不同的网络硬件,如以太网、串口WIFI、GPRS、NB-IoT、4G等通信模块。在TCP/IP网络协议栈上提供常用的物联网协议栈应用层,如COAP、MQTT,支撑终端业务快速接入腾讯云; 安全框架 :TencentOS tiny为了确保物联网终端数据传输安全以及设备认证安全,提供了比较完整的安全解决方案。安全框架提供的DTLS和TLS安全协议加固了COAP及MQTT的传输层,可确保物联网终端在对接腾讯云时实现安全认证和数据加密;另外针对低资源的终端硬件,安全框架还提供与腾讯云IoTHub配套的密钥认证方案,确保资源受限设备也能在一定程度上实现设备安全认证;物联网安全是非常重要的,这些框架也是必须存在的。 组件框架 :TencentOS tiny提供文件系统、KV存储、自组网、JS引擎、低功耗框架、设备框架、OTA、调试工具链等一系列组件,这部分我觉得还是很不错的,期待ing; 开放API (规划开发中):TencentOS tiny将在协议中间件和框架层上提供开放API函数,这样子就能很方便使用中间组件的功能,我是最喜欢这种开发的,不造轮子,能直接使用。简单来说这个API能快速对接腾讯云,实现终端业务上云的需求,最大程度减少终端物联网产品开发周期,节省开发成本; 示例应用 :TencentOS tiny提供的示例代码,模块测试代码等,方便用户参考使用。 __腾讯云物联网平台__(图中最上层的部分):严格来说这部分不算TencentOS tiny的框架内容,这是接入平台层了,腾讯云的物联网平台都是提供多种语言的SDK包,当然在嵌入式设备上肯定使用C SDK的,不过总的来说都是很不错的。 总的来说TencentOS tiny还是非常不错的,该有的功能都有,可以考虑使用一下这个操作系统~ 对于杰杰来说,作为嵌入式开发者,我是很看好物联网的,也一直在往这条路上缓缓前行,在万物互联的时代,说不定真的能在赚钱的同时还能为世界做出一丢丢贡献(如果没有那就算了)。 据我所知,TencentOS tiny后续也将推出基于事件驱动模型的调度,用于某些单片机上不支持基于上下文调度的多任务。 顺便再透露一下,我将接下来会写一系列TencentOS tiny内核分析的文章,全网首发哦!ps:得到源码作者的亲自指点,绝对干货!!! 喜欢就关注我吧! 图文教程:简单上手:超详细的 TencentOS tiny 移植到STM32F103全教程深度源码分析:【TencentOS tiny学习】源码分析(1)——task【TencentOS tiny学习】源码分析(2)——调度器【TencentOS tiny学习】源码分析(3)——队列【TencentOS tiny学习】源码分析(4)——消息队列【TencentOS tiny学习】源码分析(5)——信号量【TencentOS tiny学习】源码分析(6)——互斥锁【TencentOS tiny学习】源码分析(7)——事件【TencentOS tiny学习】源码分析(8)——软件定时器配套例程:【TencentOS tiny学习】例程(0)——hello world【TencentOS tiny学习】例程(1)——task【TencentOS tiny学习】例程(2)——队列【TencentOS tiny学习】例程(3)——消息队列【TencentOS tiny学习】例程(4)——信号量【TencentOS tiny学习】例程(5)——互斥锁【TencentOS tiny学习】例程(6)——事件【TencentOS tiny学习】例程(7)——软件定时器【TencentOS tiny学习】例程(8)——内存池【TencentOS tiny学习】例程(9)——内存堆视频教程:【TencentOS tiny学习】视频汇总【视频】01-初识TencentOS tiny【视频】02-TencentOS tiny基础知识【视频】03-TencentOS tiny移植【视频】04-TencentOS tiny任务-1【视频】05-TencentOS tiny任务-2【视频】06-TencentOS tiny队列-1【视频】07-TencentOS tiny队列-2【视频】08-TencentOS tiny消息队列【视频】09-TencentOS tiny信号量-1【视频】10-TencentOS tiny信号量-2【视频】11-TencentOS tiny互斥锁-1【视频】12-TencentOS tiny互斥锁-2【视频】13-TencentOS tiny互斥锁-3【视频】14-TencentOS tiny事件-1【视频】15-TencentOS tiny事件-2【视频】16-TencentOS tiny软件定时器-1【视频】17-TencentOS tiny软件定时器-2【视频】18-TencentOS tiny软件定时器-3相关PPT资料:【TencentOS tiny学习】视频PPT喜欢就关注我吧! ...

October 17, 2019 · 1 min · jiezi

STM32之串口DMA接收不定长数据

STM32之串口DMA接收不定长数据引言在使用stm32或者其他单片机的时候,会经常使用到串口通讯,那么如何有效地接收数据呢?假如这段数据是不定长的有如何高效接收呢? 同学A:数据来了就会进入串口中断,在中断中读取数据就行了!中断就是打断程序正常运行,怎么能保证高效呢?经常把主程序打断,主程序还要不要运行了? 同学B:串口可以配置成用DMA的方式接收数据,等接收完毕就可以去读取了! 这个同学是对的,我们可以使用DMA去接收数据,不过DMA需要定长才能产生接收中断,如何接收不定长的数据呢? DMA简介题外话:其实,上面的问题是很有必要思考一下的,不断思考,才能进步。什么是DMADMA:全称Direct Memory Access,即直接存储器访问 DMA 传输将数据从一个地址空间复制到另外一个地址空间。CPU只需初始化DMA即可,传输动作本身是由 DMA 控制器来实现和完成。典型的例子就是移动一个外部内存的区块到芯片内部更快的内存区。这样的操作并没有让处理器参与处理,CPU可以干其他事情,当DMA传输完成的时候产生一个中断,告诉CPU我已经完成了,然后CPU知道了就可以去处理数据了,这样子提高了CPU的利用率,因为CPU是大脑,主要做数据运算的工作,而不是去搬运数据。DMA 传输对于高效能嵌入式系统算法和网络是很重要的。 在STM32的DMA资源STM32F1系列的MCU有两个DMA控制器(DMA2只存在于大容量产品中),DMA1有7个通道,DMA2有5个通道,每个通道专门用来管理来自于一个或者多个外设对存储器的访问请求。还有一个仲裁器来协调各个DMA请求的优先权。 而STM32F4/F7/H7系列的MCU有两个DMA控制器总共有16个数据流(每个DMA控制器8个),每一个DMA控制器都用于管理一个或多个外设的存储器访问请求。每个数据流总共可以有多达8个通道(或称请求)。每个通道都有一个仲裁器,用于处理 DMA 请求间的优先级。 DMA接收数据DMA在接收数据的时候,串口接收DMA在初始化的时候就处于开启状态,一直等待数据的到来,在软件上无需做任何事情,只要在初始化配置的时候设置好配置就可以了。等到接收到数据的时候,告诉CPU去处理即可。 判断数据接收完成那么问题来了,怎么知道数据是否接收完成呢?其实,有很多方法: 对于定长的数据,只需要判断一下数据的接收个数,就知道是否接收完成,这个很简单,暂不讨论。对于不定长的数据,其实也有好几种方法,麻烦的我肯定不会介绍,有兴趣做复杂工作的同学可以在网上看看别人怎么做,下面这种方法是最简单的,充分利用了stm32的串口资源,效率也是非常之高。DMA+串口空闲中断 这两个资源配合,简直就是天衣无缝啊,无论接收什么不定长的数据,管你数据有多少,来一个我就收一个,就像广东人吃“山竹”,来一个吃一个~(最近风好大,我好怕)。 可能很多人在学习stm32的时候,都不知道idle是啥东西,先看看stm32串口的状态寄存器: 当我们检测到触发了串口总线空闲中断的时候,我们就知道这一波数据传输完成了,然后我们就能得到这些数据,去进行处理即可。这种方法是最简单的,根本不需要我们做多的处理,只需要配置好,串口就等着数据的到来,dma也是处于工作状态的,来一个数据就自动搬运一个数据。 接收完数据时处理串口接收完数据是要处理的,那么处理的步骤是怎么样呢? 暂时关闭串口接收DMA通道,有两个原因:1.防止后面又有数据接收到,产生干扰,因为此时的数据还未处理。2.DMA需要重新配置。清DMA标志位。从DMA寄存器中获取接收到的数据字节数(可有可无)。重新设置DMA下次要接收的数据字节数,注意,数据传输数量范围为0至65535。这个寄存器只能在通道不工作(DMA_CCRx的EN=0)时写入。通道开启后该寄存器变为只读,指示剩余的待传输字节数目。寄存器内容在每次DMA传输后递减。数据传输结束后,寄存器的内容或者变为0;或者当该通道配置为自动重加载模式时,寄存器的内容将被自动重新加载为之前配置时的数值。当寄存器的内容为0时,无论通道是否开启,都不会发生任何数据传输。给出信号量,发送接收到新数据标志,供前台程序查询。开启DMA通道,等待下一次的数据接收,注意,对DMA的相关寄存器配置写入,如重置DMA接收数据长度,必须要在关闭DMA的条件进行,否则操作无效。注意事项 STM32的IDLE的中断在串口无数据接收的情况下,是不会一直产生的,产生的条件是这样的,当清除IDLE标志位后,必须有接收到第一个数据后,才开始触发,一断接收的数据断流,没有接收到数据,即产生IDLE中断。如果中断发送数据帧的速率很快,MCU来不及处理此次接收到的数据,中断又发来数据的话,这里不能开启,否则数据会被覆盖。有两种方式解决: 在重新开启接收DMA通道之前,将Rx_Buf缓冲区里面的数据复制到另外一个数组中,然后再开启DMA,然后马上处理复制出来的数据。建立双缓冲,重新配置DMA_MemoryBaseAddr的缓冲区地址,那么下次接收到的数据就会保存到新的缓冲区中,不至于被覆盖。程序实现实验效果:当外部给单片机发送数 据的时候,假设这帧数据长度是1000个字节,那么在单片机接收到一个字节的时候并不会产生串口中断,只是DMA在背后默默地把数据搬运到你指定的缓冲区里面。当整帧数据发送完毕之后串口才会产生一次中断,此时可以利用DMA_GetCurrDataCounter()函数计算出本次的数据接受长度,从而进行数据处理。 串口的配置很简单,基本与使用串口的时候一致,只不过一般我们是打开接收缓冲区非空中断,而现在是打开空闲中断——USART_ITConfig(DEBUG_USARTx, USART_IT_IDLE, ENABLE); 。 /** * @brief USART GPIO 配置,工作参数配置 * @param 无 * @retval 无 */void USART_Config(void){ GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; // 打开串口GPIO的时钟 DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE); // 打开串口外设的时钟 DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE); // 将USART Tx的GPIO配置为推挽复用模式 GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure); // 将USART Rx的GPIO配置为浮空输入模式 GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure); // 配置串口的工作参数 // 配置波特率 USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE; // 配置 针数据字长 USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 配置停止位 USART_InitStructure.USART_StopBits = USART_StopBits_1; // 配置校验位 USART_InitStructure.USART_Parity = USART_Parity_No ; // 配置硬件流控制 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 配置工作模式,收发一起 USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 完成串口的初始化配置 USART_Init(DEBUG_USARTx, &USART_InitStructure); // 串口中断优先级配置 NVIC_Configuration(); #if USE_USART_DMA_RX // 开启 串口空闲IDEL 中断 USART_ITConfig(DEBUG_USARTx, USART_IT_IDLE, ENABLE); // 开启串口DMA接收 USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Rx, ENABLE); /* 使能串口DMA */ USARTx_DMA_Rx_Config();#else // 使能串口接收中断 USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE); #endif#if USE_USART_DMA_TX // 开启串口DMA发送// USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Tx, ENABLE); USARTx_DMA_Tx_Config();#endif // 使能串口 USART_Cmd(DEBUG_USARTx, ENABLE); }串口DMA配置 ...

October 17, 2019 · 2 min · jiezi

STM32进阶之串口环形缓冲区实现

队列的概念在此之前,我们来回顾一下队列的基本概念: 队列 (Queue):是一种先进先出(First In First Out ,简称 FIFO)的线性表,只允许在一端插入(入队),在另一端进行删除(出队)。 队列的特点类似售票排队窗口,先到的人看到能先买到票,然后先走,后来的人只能后买到票 队列的常见两种形式 普通队列 在计算机中,每个信息都是存储在存储单元中的,比喻一下吧,上图的一些小正方形格子就是一个个存储单元,你可以理解为常见的数组,存放我们一个个的信息。 当有大量数据的时候,我们不能存储所有的数据,那么计算机处理数据的时候,只能先处理先来的,那么处理完后呢,就会把数据释放掉,再处理下一个。那么,已经处理的数据的内存就会被浪费掉。因为后来的数据只能往后排队,如过要将剩余的数据都往前移动一次,那么效率就会低下了,肯定不现实,所以,环形队列就出现了。 环形队列 它的队列就是一个环,它避免了普通队列的缺点,就是有点难理解而已,其实它就是一个队列,一样有队列头,队列尾,一样是先进先出(FIFO)。我们采用顺时针的方式来对队列进行排序。 队列头 (Head) : 允许进行删除的一端称为队首。队列尾 (Tail) : 允许进行插入的一端称为队尾。环形队列的实现:在计算机中,也是没有环形的内存的,只不过是我们将顺序的内存处理过,让某一段内存形成环形,使他们首尾相连,简单来说,这其实就是一个数组,只不过有两个指针,一个指向列队头,一个指向列队尾。指向列队头的指针(Head)是缓冲区可读的数据,指向列队尾的指针(Tail)是缓冲区可写的数据,通过移动这两个指针(Head) &(Tail)即可对缓冲区的数据进行读写操作了,直到缓冲区已满(头尾相接),将数据处理完,可以释放掉数据,又可以进行存储新的数据了。 实现的原理:初始化的时候,列队头与列队尾都指向0,当有数据存储的时候,数据存储在‘0’的地址空间,列队尾指向下一个可以存储数据的地方‘1’,再有数据来的时候,存储数据到地址‘1’,然后队列尾指向下一个地址‘2’。当数据要进行处理的时候,肯定是先处理‘0’空间的数据,也就是列队头的数据,处理完了数据,‘0’地址空间的数据进行释放掉,列队头指向下一个可以处理数据的地址‘1’。从而实现整个环形缓冲区的数据读写。 看图,队列头就是指向已经存储的数据,并且这个数据是待处理的。下一个CPU处理的数据就是1;而队列尾则指向可以进行写数据的地址。当1处理了,就会把1释放掉。并且把队列头指向2。当写入了一个数据6,那么队列尾的指针就会指向下一个可以写的地址。 从队列到串口缓冲区的实现串口环形缓冲区收发:在很多入门级教程中,我们知道的串口收发都是:接收一个数据,触发中断,然后把数据发回来。这种处理方式是没有缓冲的,当数量太大的时候,亦或者当数据接收太快的时候,我们来不及处理已经收到的数据,那么,当再次收到数据的时候,就会将之前还未处理的数据覆盖掉。那么就会出现丢包的现象了,对我们的程序是一个致命的创伤。 那么如何避免这种情况的发生呢,很显然,上面说的一些队列的特性很容易帮我们实现我们需要的情况。将接受的数据缓存一下,让处理的速度有些许缓冲,使得处理的速度赶得上接收的速度,上面又已经分析了普通队列与环形队列的优劣了,那么我们肯定是用环形队列来进行实现了。下面就是代码的实现: 定义一个结构体:typedef struct{    u16 Head;              u16 Tail;    u16 Lenght;    u8 Ring_Buff[RINGBUFF_LEN];}RingBuff_t;RingBuff_t ringBuff;//创建一个ringBuff的缓冲区初始化初始化结构体相关信息:使得我们的环形缓冲区是头尾相连的,并且里面没有数据,也就是空的队列。 /** * @brief  RingBuff_Init * @param  void * @return void * @author 杰杰 * @date   2018 * @version v1.0 * @note   初始化环形缓冲区 */void RingBuff_Init(void){   //初始化相关信息   ringBuff.Head = 0;   ringBuff.Tail = 0;   ringBuff.Lenght = 0;}初始化效果如下: ...

October 17, 2019 · 2 min · jiezi

一种CortexM内核中的精确延时方法

本文介绍一种Cortex-M内核中的精确延时方法前言为什么要学习这种延时的方法? 很多时候我们跑操作系统,就一般会占用一个硬件定时器——SysTick,而我们一般操作系统的时钟节拍一般是设置100-1000HZ,也就是1ms——10ms产生一次中断。很多裸机教程使用延时函数又是基于SysTick的,这样一来又难免产生冲突。很多人会说,不是还有定时器吗,定时器的计时是超级精确的。这点我不否认,但是假设,如果一个系统,总是进入定时器中断(10us一次/1us一次/0.5us一次),那整个系统就会经常被打断,线程的进行就没办法很好运行啊。此外还消耗一个硬件定时器资源,一个硬件定时器可能做其他事情呢!对应ST HAL库的修改,其实杰杰个人觉得吧,ST的东西什么都好,就是出的HAL库太恶心了,没办法,而HAL库中有一个HAL_Delay(),他也是采用SysTick延时的,在移植操作系统的时候,会有诸多不便,不过好在,HAL_Delay()是一个弱定义的,我们可以重写这个函数的实现,那么,采用内核延时当然是最好的办法啦(个人是这么觉得的)当然你有能力完全用for循环写个简单的延时还是可以的。可能我说的话没啥权威,那我就引用Cortex-M3权威指南中的一句话——“DWT 中有剩余的计数器,它们典型地用于程序代码的“性能速写”(profiling)。通过编程它们,就可以让它们在计数器溢出时发出事件(以跟踪数据包的形式)。最典型地,就是使用 CYCCNT寄存器来测量执行某个任务所花的周期数,这也可以用作时间基准相关的目的(操作系统中统计 CPU使用率可以用到它)。”Cortex-M中的DWT在Cortex-M里面有一个外设叫DWT(Data Watchpoint and Trace),是用于系统调试及跟踪,它有一个32位的寄存器叫CYCCNT,它是一个向上的计数器,记录的是内核时钟运行的个数,内核时钟跳动一次,该计数器就加1,精度非常高,决定内核的频率是多少,如果是F103系列,内核时钟是72M,那精度就是1/72M = 14ns,而程序的运行时间都是微秒级别的,所以14ns的精度是远远够的。最长能记录的时间为:60s=2的32次方/72000000(假设内核频率为72M,内核跳一次的时间大概为1/72M=14ns),而如果是H7这种400M主频的芯片,那它的计时精度高达2.5ns(1/400000000 = 2.5),而如果是 i.MX RT1052这种比较牛逼的处理器,最长能记录的时间为: 8.13s=2的32次方/528000000 (假设内核频率为528M,内核跳一次的时间大概为1/528M=1.9ns) 。当CYCCNT溢出之后,会清0重新开始向上计数。 m3、m4、m7杰杰实测可用(m0不可用)。 精度:1/内核频率(s)。要实现延时的功能,总共涉及到三个寄存器:DEMCR 、DWT_CTRL、DWT_CYCCNT,分别用于开启DWT功能、开启CYCCNT及获得系统时钟计数值。 DEMCR想要使能DWT外设,需要由另外的内核调试寄存器DEMCR的位24控制,写1使能(划重点啦,要考试!!)。 DEMCR的地址是0xE000 EDFC 关于DWT_CYCCNT使能DWT_CYCCNT寄存器之前,先清0。让我们看看DWT_CYCCNT的基地址,从ARM-Cortex-M手册中可以看到其基地址是0xE000 1004,复位默认值是0,而且它的类型是可读可写的,我们往0xE000 1004这个地址写0就将DWT_CYCCNT清0了。 关于CYCCNTENACYCCNTENA Enable the CYCCNT counter. If not enabled, the counter does not count and no event isgenerated for PS sampling or CYCCNTENA. In normal use, the debugger must initializethe CYCCNT counter to 0.它是DWT控制寄存器的第一位,写1使能,则启用CYCCNT计数器,否则CYCCNT计数器将不会工作。 综上所述想要使用DWT的CYCCNT步骤: 先使能DWT外设,这个由另外内核调试寄存器DEMCR的位24控制,写1使能使能CYCCNT寄存器之前,先清0。使能CYCCNT寄存器,这个由DWT的CYCCNTENA 控制,也就是DWT控制寄存器的位0控制,写1使能代码实现/** ****************************************************************** * @file core_delay.c * @author fire * @version V1.0 * @date 2018-xx-xx * @brief 使用内核寄存器精确延时 ****************************************************************** * @attention * * 实验平台:野火 STM32开发板 * 论坛 :http://www.firebbs.cn * 淘宝 :https://fire-stm32.taobao.com * ****************************************************************** */ #include "./delay/core_delay.h" /************************************************************************ 时间戳相关寄存器定义***********************************************************************//* 在Cortex-M里面有一个外设叫DWT(Data Watchpoint and Trace), 该外设有一个32位的寄存器叫CYCCNT,它是一个向上的计数器, 记录的是内核时钟运行的个数,最长能记录的时间为: 10.74s=2的32次方/400000000 (假设内核频率为400M,内核跳一次的时间大概为1/400M=2.5ns) 当CYCCNT溢出之后,会清0重新开始向上计数。 使能CYCCNT计数的操作步骤: 1、先使能DWT外设,这个由另外内核调试寄存器DEMCR的位24控制,写1使能 2、使能CYCCNT寄存器之前,先清0 3、使能CYCCNT寄存器,这个由DWT_CTRL(代码上宏定义为DWT_CR)的位0控制,写1使能 */#define DWT_CR *(__IO uint32_t *)0xE0001000#define DWT_CYCCNT *(__IO uint32_t *)0xE0001004#define DEM_CR *(__IO uint32_t *)0xE000EDFC#define DEM_CR_TRCENA (1 << 24)#define DWT_CR_CYCCNTENA (1 << 0)/** * @brief 初始化时间戳 * @param 无 * @retval 无 * @note 使用延时函数前,必须调用本函数 */HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority){ /* 使能DWT外设 */ DEM_CR |= (uint32_t)DEM_CR_TRCENA; /* DWT CYCCNT寄存器计数清0 */ DWT_CYCCNT = (uint32_t)0u; /* 使能Cortex-M DWT CYCCNT寄存器 */ DWT_CR |= (uint32_t)DWT_CR_CYCCNTENA; return HAL_OK;}/** * @brief 读取当前时间戳 * @param 无 * @retval 当前时间戳,即DWT_CYCCNT寄存器的值 */uint32_t CPU_TS_TmrRd(void){ return ((uint32_t)DWT_CYCCNT);}/** * @brief 读取当前时间戳 * @param 无 * @retval 当前时间戳,即DWT_CYCCNT寄存器的值 */uint32_t HAL_GetTick(void){ return ((uint32_t)DWT_CYCCNT/SysClockFreq*1000);}/** * @brief 采用CPU的内部计数实现精确延时,32位计数器 * @param us : 延迟长度,单位1 us * @retval 无 * @note 使用本函数前必须先调用CPU_TS_TmrInit函数使能计数器, 或使能宏CPU_TS_INIT_IN_DELAY_FUNCTION 最大延时值为8秒,即8*1000*1000 */void CPU_TS_Tmr_Delay_US(uint32_t us){ uint32_t ticks; uint32_t told,tnow,tcnt=0; /* 在函数内部初始化时间戳寄存器, */ #if (CPU_TS_INIT_IN_DELAY_FUNCTION) /* 初始化时间戳并清零 */ HAL_InitTick(5);#endif ticks = us * (GET_CPU_ClkFreq() / 1000000); /* 需要的节拍数 */ tcnt = 0; told = (uint32_t)CPU_TS_TmrRd(); /* 刚进入时的计数器值 */ while(1) { tnow = (uint32_t)CPU_TS_TmrRd(); if(tnow != told) { /* 32位计数器是递增计数器 */ if(tnow > told) { tcnt += tnow - told; } /* 重新装载 */ else { tcnt += UINT32_MAX - told + tnow; } told = tnow; /*时间超过/等于要延迟的时间,则退出 */ if(tcnt >= ticks)break; } }}/*********************************************END OF FILE**********************/#ifndef __CORE_DELAY_H#define __CORE_DELAY_H#include "stm32h7xx.h"/* 获取内核时钟频率 */#define GET_CPU_ClkFreq() HAL_RCC_GetSysClockFreq()#define SysClockFreq (218000000)/* 为方便使用,在延时函数内部调用CPU_TS_TmrInit函数初始化时间戳寄存器, 这样每次调用函数都会初始化一遍。 把本宏值设置为0,然后在main函数刚运行时调用CPU_TS_TmrInit可避免每次都初始化 */ #define CPU_TS_INIT_IN_DELAY_FUNCTION 0 /******************************************************************************* * 函数声明 ******************************************************************************/uint32_t CPU_TS_TmrRd(void);HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority);//使用以下函数前必须先调用CPU_TS_TmrInit函数使能计数器,或使能宏CPU_TS_INIT_IN_DELAY_FUNCTION//最大延时值为8秒void CPU_TS_Tmr_Delay_US(uint32_t us);#define HAL_Delay(ms) CPU_TS_Tmr_Delay_US(ms*1000)#define CPU_TS_Tmr_Delay_S(s) CPU_TS_Tmr_Delay_MS(s*1000)#endif /* __CORE_DELAY_H */注意事项:使用者如果不是在HAL库中使用,注释掉: ...

October 17, 2019 · 2 min · jiezi

单片机C语言知识用法之define

define的定义:define是C语言中的一个预处理指令,其中的“#”表示这是一条预处理命令·。凡是以“#”开头的均为预处理命令,“define”为宏定义命令,“标识符”为所定义的宏名。#define TIME_NUM  1000  //定义一个时间数一个简单的TIME_NUM 就定义好了,它代表1000,如果在程序里面写: if(i<TIME_NUM ){ .... ....}编译器在处理这个代码之前会对TIME_NUM 进行处理替换为1000。这样的定义看起来类似于普通的常量定义CONST,但也有着不同,因为define的定义更像是简单的文本替换,而不是作为一个量来使用。 define的语法:语法一:#define      标识符    被标识符代表的字符串      //这种用法很简单,就是一种替换.语法二:#define     标识符[(参数1,.....,参数n)]      被标识符代表的字符串        //其中,在 "被标识符代表的字符串" 中出现的形参将在使用时被实参替代. 就像写函数一样.例如: #define    ADD_NUM     10   也可以这样子用: #define    ADD(x,y)     (x+y)这个定义就将返回两个数中较大的那个,看到了吗?因为这个“函数”没有类型检查,就好像一个函数模板似的,当然,它绝对没有模板那么安全就是了。可以作为一个简单的模板来使用而已。但是这样做的话存在隐患,例子如下: #define ADD(a,b)   a+b在一般使用的时候是没有问题的,但是如果遇到如:c Add(a,b) d的时候就会出现问题,代数式的本意是a+b然后去和c,d相乘,但是因为使用了define(它只是一个简单的替换),所以式子实际上变成了  ca + bd 所以,用#define要注意顺序 一般我个人用#define在单片机程序上的话,我一般只做简单的替换。 #define TIME_NUM   (60*60*24)UL//定义一个一天时间有多少秒另外举一个例子: #define pin (int*);pin a,b;本意是a和b都是int型指针,但是实际上变成int* a,b;a是int型指针,而b是int型变量。这是应该使用typedef来代替define,这样a和b就都是int型指针了。所以我们在定义的时候,养成一个良好的习惯,建议所有的层次都要加括号。 而且,宏在单片机代码中用的很多,常数的替换、地址的偏移,等等都用得上用宏来修改移植代码更加便捷,代码更容易使人读懂。。。。 喜欢就关注我吧! 相关代码可以在公众号后台获取。 欢迎关注“物联网IoT开发”公众号

October 17, 2019 · 1 min · jiezi

基于Linux的kfifo移植到STM32支持os的互斥访问

基于Linux的kfifo移植到STM32(支持os的互斥访问)关于kfifokfifo是内核里面的一个First In First Out数据结构,它采用环形循环队列的数据结构来实现;它提供一个无边界的字节流服务,最重要的一点是,它使用并行无锁编程技术,即当它用于只有一个入队线程和一个出队线程的场情时,两个线程可以并发操作,而不需要任何加锁行为,就可以保证kfifo的线程安全。 具体什么是环形缓冲区,请看我以前的文章说明关于kfifo的相关概念我不会介绍,有兴趣可以看他的相关文档,我只将其实现过程移植重写,移植到适用stm32开发板上,并且按照我个人习惯重新命名,RingBuff->意为环形缓冲区RingBuff_t环形缓冲区的结构体成员变量,具体含义看注释。 buffer: 用于存放数据的缓存 size: buffer空间的大小 in, out: 和buffer一起构成一个循环队列。 in指向buffer中队头,而且out指向buffer中的队尾 typedef struct ringbuff { uint8_t *buffer; /* 数据区域 */ uint32_t size; /* 环形缓冲区大小 */ uint32_t in; /* 数据入队指针 (in % size) */ uint32_t out; /* 数据出队指针 (out % size) */#if USE_MUTEX MUTEX_T *mutex; /* 支持rtos的互斥 */#endif}RingBuff_t ;Create_RingBuff创建一个环形缓冲区,为了适应后续对缓冲区入队出队的高效操作,环形缓冲区的大小应为2^n字节,如果不是这个大小,则系统默认裁剪以对应缓冲区字节。当然还可以优化,不过我目前并未做,思路如下:如果系统支持动态分配内存,则向上对齐,避免浪费内存空间,否则就按照我默认的向下对齐,当内存越大,对齐导致内存泄漏则会越多。对齐采用的函数是roundup_pow_of_two。如果系统支持互斥量,那么还将创建一个互斥量用来做互斥访问,防止多线程同时使用导致数据丢失。 /************************************************************ * @brief Create_RingBuff * @param rb:环形缓冲区句柄 * buffer:环形缓冲区的数据区域 * size:环形缓冲区的大小,缓冲区大小要为2^n * @return err_t:ERR_OK表示创建成功,其他表示失败 * @author jiejie * @github https://github.com/jiejieTop * @date 2018-xx-xx * @version v1.0 * @note 用于创建一个环形缓冲区 ***********************************************************/err_t Create_RingBuff(RingBuff_t* rb, uint8_t *buffer, uint32_t size ){ if((rb == NULL)||(buffer == NULL)||(size == 0)) { PRINT_ERR("data is null!"); return ERR_NULL; } PRINT_DEBUG("ringbuff size is %d!",size); /* 缓冲区大小必须为2^n字节,系统会强制转换, 否则可能会导致指针访问非法地址。 空间大小越大,强转时丢失内存越多 */ if(size&(size - 1)) { size = roundup_pow_of_two(size); PRINT_DEBUG("change ringbuff size is %d!",size); } rb->buffer = buffer; rb->size = size; rb->in = rb->out = 0;#if USE_MUTEX /* 创建信号量不成功 */ if(!create_mutex(rb->mutex)) { PRINT_ERR("create mutex fail!"); ASSERT(ASSERT_ERR); return ERR_NOK; }#endif PRINT_DEBUG("create ringBuff ok!"); return ERR_OK;}roundup_pow_of_two/************************************************************ * @brief roundup_pow_of_two * @param size:传递进来的数据长度 * @return size:返回处理之后的数据长度 * @author jiejie * @github https://github.com/jiejieTop * @date 2018-xx-xx * @version v1.0 * @note 用于处理数据,使数据长度必须为 2^n * 如果不是,则转换,丢弃多余部分,如 * roundup_pow_of_two(66) -> 返回 64 ***********************************************************/static unsigned long roundup_pow_of_two(unsigned long x){ return (1 << (fls(x-1)-1)); //向下对齐 //return (1UL << fls(x - 1)); //向上对齐,用动态内存可用使用}Delete_RingBuff删除一个环形缓冲区,删除之后,缓冲区真正存储地址是不会被改变的(目前我是使用自定义数组做缓冲区的),但是删除之后,就无法对缓冲区进行读写操作。并且如果支持os的话,创建的互斥量会被删除。 ...

October 17, 2019 · 5 min · jiezi

纯C语言写的按键驱动将按键逻辑与按键处理事件分离

button drive杰杰自己写的一个按键驱动,支持单双击、连按、长按;采用回调处理按键事件(自定义消抖时间),使用只需3步,创建按键,按键事件与回调处理函数链接映射,周期检查按键。 源码地址:https://github.com/jiejieTop/ButtonDrive。作者:杰杰 前言前几天写了个按键驱动,参考了MulitButton的数据结构的用法,逻辑实现并不一样。在这里感谢所有的开源开发者,让我从中学到了很多,同时网络也是一个好平台,也希望所有的开发者能形成良性循环,从网络中学知识,回馈到网络中去。感谢MulitButton的作者0x1abin,感谢两位rtt的大佬:大法师、流光。 Button_drive简介Button_drive是一个小巧的按键驱动,支持单击、双击、长按、连续触发等(后续可以在按键控制块中添加触发事件),理论上可无限量扩展Button,Button_drive采用按键触发事件回调方式处理业务逻辑,支持在RTOS中使用,我目前仅在RT-Thread上测试过。写按键驱动的目的是想要将用户按键逻辑与按键处理事件分离,用户无需处理复杂麻烦的逻辑事件。 Button_drive使用效果单击与长按[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2nhPnwqF-1571148176402)(https://github.com/jiejieTop/...] 双击[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wtGvjTjq-1571148176403)(https://github.com/jiejieTop/...] 连按[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QUypOh2s-1571148176403)(https://github.com/jiejieTop/...] 连按释放[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LfsbuM1W-1571148176404)(https://github.com/jiejieTop/...] 使用方法创建按键句柄Button_t Button1;Button_t Button2; 创建按键,初始化按键信息,包括按键名字、按键电平检测函数接口、按键触发电平。 Button_Create("Button1", //按键名字 &Button1, //按键句柄 Read_Button1_Level, //按键电平检测函数接口 BTN_TRIGGER); //触发电平 ......按键触发事件与事件回调函数链接映射,当按键事件被触发的时候,自动跳转回调函数中处理业务逻辑。 Button_Attach(&Button1,BUTTON_DOWM,Btn2_Dowm_CallBack); //按键单击 Button_Attach(&Button1,BUTTON_DOUBLE,Btn2_Double_CallBack); //双击 Button_Attach(&Button1,BUTTON_LONG,Btn2_Long_CallBack); //长按 .......周期调用回调按键处理函数即可,建议调用周期20-50ms。Button_Process(); //需要周期调用按键处理函数需要用户实现的 2 个函数: 按键电平检测接口:uint8_t Read_Button1_Level(void){ return GPIO_ReadInputDataBit(BTN1_GPIO_PORT,BTN1_GPIO_PIN);}uint8_t Read_Button2_Level(void){ return GPIO_ReadInputDataBit(BTN2_GPIO_PORT,BTN2_GPIO_PIN);}// 这是我在stm32上简单测试的伪代码,以实际源码为准按键逻辑处理void Btn1_Dowm_CallBack(void *btn){ PRINT_INFO("Button1 单击!");}void Btn1_Double_CallBack(void *btn){ PRINT_INFO("Button1 双击!");}void Btn1_Long_CallBack(void *btn){ PRINT_INFO("Button1 长按!"); Button_Delete(&Button2); PRINT_INFO("删除Button1"); Search_Button();}特点Button_drive开放源码,按键控制块采用数据结构方式,按键事件采用枚举类型,确保不会重复,也便于添加用户需要逻辑,采用宏定义方式定义消抖时间、连按触发时间、双击时间间隔、长按时间等,便于修改。同时所有被创建的按键采用单链表方式连击,用户只管创建,无需理会按键处理,只需调用Button_Process()即可,在函数中会自动遍历所有被创建的按键。支持按键删除操作,用户无需在代码中删除对应的按键创建于映射链接代码,也无需删除关于按键的任何回调事件处理函数,只需调用Button_Delete()函数即可,这样子,就不会处理关于被删除按键的任何状态。当然目前按键内存不会释放,如果使用os的话,建议释放按键内存。 按键控制块/* 每个按键对应1个全局的结构体变量。 其成员变量是实现消抖和多种按键状态所必须的*/typedef struct button{ /* 下面是一个函数指针,指向判断按键手否按下的函数 */ uint8_t (*Read_Button_Level)(void); /* 读取按键电平函数,需要用户实现 */ char Name[BTN_NAME_MAX]; uint8_t Button_State : 4; /* 按键当前状态(按下还是弹起) */ uint8_t Button_Last_State : 4; /* 上一次的按键状态,用于判断双击 */ uint8_t Button_Trigger_Level : 2; /* 按键触发电平 */ uint8_t Button_Last_Level : 2; /* 按键当前电平 */ uint8_t Button_Trigger_Event; /* 按键触发事件,单击,双击,长按等 */ Button_CallBack CallBack_Function[number_of_event]; uint8_t Button_Cycle; /* 连续按键周期 */ uint8_t Timer_Count; /* 计时 */ uint8_t Debounce_Time; /* 消抖时间 */ uint8_t Long_Time; /* 按键按下持续时间 */ struct button *Next; }Button_t;触发事件typedef enum { BUTTON_DOWM = 0, BUTTON_UP, BUTTON_DOUBLE, BUTTON_LONG, BUTTON_CONTINUOS, BUTTON_CONTINUOS_FREE, BUTTON_ALL_RIGGER, number_of_event, /* 触发回调的事件 */ NONE_TRIGGER}Button_Event;宏定义选择#define BTN_NAME_MAX 32 //名字最大为32字节/* 按键消抖时间40ms, 建议调用周期为20ms 只有连续检测到40ms状态不变才认为有效,包括弹起和按下两种事件*/#define CONTINUOS_TRIGGER 0 //是否支持连续触发,连发的话就不要检测单双击与长按了 /* 是否支持单击&双击同时存在触发,如果选择开启宏定义的话,单双击都回调,只不过单击会延迟响应, 因为必须判断单击之后是否触发了双击否则,延迟时间是双击间隔时间 BUTTON_DOUBLE_TIME。 而如果不开启这个宏定义,建议工程中只存在单击/双击中的一个,否则,在双击响应的时候会触发一次单击, 因为双击必须是有一次按下并且释放之后才产生的 */#define SINGLE_AND_DOUBLE_TRIGGER 1 /* 是否支持长按释放才触发,如果打开这个宏定义,那么长按释放之后才触发单次长按, 否则在长按指定时间就一直触发长按,触发周期由 BUTTON_LONG_CYCLE 决定 */#define LONG_FREE_TRIGGER 0 #define BUTTON_DEBOUNCE_TIME 2 //消抖时间 (n-1)*调用周期#define BUTTON_CONTINUOS_CYCLE 1 //连按触发周期时间 (n-1)*调用周期 #define BUTTON_LONG_CYCLE 1 //长按触发周期时间 (n-1)*调用周期 #define BUTTON_DOUBLE_TIME 15 //双击间隔时间 (n-1)*调用周期 建议在200-600ms#define BUTTON_LONG_TIME 50 /* 持续n秒((n-1)*调用周期 ms),认为长按事件 */#define TRIGGER_CB(event) \ if(btn->CallBack_Function[event]) \ btn->CallBack_Function[event]((Button_t*)btn)例子 Button_Create("Button1", &Button1, Read_KEY1_Level, KEY_ON); Button_Attach(&Button1,BUTTON_DOWM,Btn1_Dowm_CallBack); //单击 Button_Attach(&Button1,BUTTON_DOUBLE,Btn1_Double_CallBack); //双击 Button_Attach(&Button1,BUTTON_CONTINUOS,Btn1_Continuos_CallBack); //连按 Button_Attach(&Button1,BUTTON_CONTINUOS_FREE,Btn1_ContinuosFree_CallBack); //连按释放 Button_Attach(&Button1,BUTTON_LONG,Btn1_Long_CallBack); //长按 Button_Create("Button2", &Button2, Read_KEY2_Level, KEY_ON); Button_Attach(&Button2,BUTTON_DOWM,Btn2_Dowm_CallBack); //单击 Button_Attach(&Button2,BUTTON_DOUBLE,Btn2_Double_CallBack); //双击 Button_Attach(&Button2,BUTTON_CONTINUOS,Btn2_Continuos_CallBack); //连按 Button_Attach(&Button2,BUTTON_CONTINUOS_FREE,Btn2_ContinuosFree_CallBack); //连按释放 Button_Attach(&Button2,BUTTON_LONG,Btn2_Long_CallBack); //长按 Get_Button_Event(&Button1); Get_Button_Event(&Button2);后续流光大佬的要求,让我玩一玩RTT的rtkpgs,打算用Button_drive练一练手吧。 ...

October 17, 2019 · 2 min · jiezi

TencentOS-tiny深度源码分析8软件定时器

软件定时器的基本概念TencentOS tiny 的软件定时器是由操作系统提供的一类系统接口,它构建在硬件定时器基础之上,使系统能够提供不受硬件定时器资源限制的定时器服务,本质上软件定时器的使用相当于扩展了定时器的数量,允许创建更多的定时业务,它实现的功能与硬件定时器也是类似的。 硬件定时器是芯片本身提供的定时功能。一般是由外部晶振提供给芯片输入时钟,芯片向软件模块提供一组配置寄存器,接受控制输入,到达设定时间值后芯片中断控制器产生时钟中断。硬件定时器的精度一般很高,可以达到纳秒级别,并且是中断触发方式。软件定时器的超时处理是指:在定时时间到达之后就会自动触发一个超时,然后系统跳转到对应的函数去处理这个超时,此时,调用的函数也被称回调函数。 回调函数的执行环境可以是中断,也可以是任务,这就需要你自己在tos_config.h通过TOS_CFG_TIMER_AS_PROC宏定义选择回调函数的执行环境了。 TOS_CFG_TIMER_AS_PROC 为 1 :回调函数的执行环境是中断TOS_CFG_TIMER_AS_PROC 为 0 :回调函数的执行环境是任务这与硬件定时器的中断服务函数很类似,无论是在中断中还是在任务中,回调函数的处理尽可能简短,快进快出。 软件定时器在被创建之后,当经过设定的超时时间后会触发回调函数,定时精度与系统时钟的周期有关,一般可以采用SysTick作为软件定时器的时基(在m核单片机中几乎都是采用SysTick作为系统时基,而软件定时器又是基于系统时基之上)。 TencentOS tiny提供的软件定时器支持单次模式和周期模式,单次模式和周期模式的定时时间到之后都会调用软件定时器的回调函数。 单次模式:当用户创建了定时器并启动了定时器后,指定超时时间到达,只执行一次回调函数之后就将该定时器停止,不再重新执行。周期模式:这个定时器会按照指定的定时时间循环执行回调函数,直到将定时器删除。在很多应用中,可能需要一些定时器任务,硬件定时器受硬件的限制,数量上不足以满足用户的实际需求,无法提供更多的定时器,可以采用软件定时器,由软件定时器代替硬件定时器任务。但需要注意的是软件定时器的精度是无法和硬件定时器相比的,因为在软件定时器的定时过程中是极有可能被其他中断打断,因此软件定时器更适用于对时间精度要求不高的任务。软件定时器以tick为基本计时单位,当用户创建并启动一个软件定时器时, TencentOS tiny会根据当前系统tick与用户指定的超时时间计算出该定时器超时的时间expires,并将该定时器插入软件定时器列表。 软件定时器的数据结构以下软件定时器的相关数据结构都在tos_global.c中定义软件定时器列表软件定时器列表用于记录系统中所有的软件定时器,这些软件定时器将按照唤醒时间升序插入软件定时器列表k_timer_ctl.list 中,它的数据类型是timer_ctl_t。 timer_ctl_t k_timer_ctl = { TOS_TIME_FOREVER, TOS_LIST_NODE(k_timer_ctl.list) };typedef struct timer_control_st { k_tick_t next_expires; k_list_t list;} timer_ctl_t;next_expires:记录下一个到期的软件定时器时间。list:软件定时器列表,所有的软件定时器都会被挂载到这个列表中。软件定时器任务相关的数据结构如果 TOS_CFG_TIMER_AS_PROC 宏定义为0,则表示使用软件定时器任务处理软件定时器的回调函数,那么此时软件定时器的回调函数执行环境为任务;反之软件定时器回调函数的处理将在中断上下文环境中。 k_task_t k_timer_task;k_stack_t k_timer_task_stk[TOS_CFG_TIMER_TASK_STK_SIZE];k_prio_t const k_timer_task_prio = TOS_CFG_TIMER_TASK_PRIO;k_stack_t *const k_timer_task_stk_addr = &k_timer_task_stk[0];size_t const k_timer_task_stk_size = TOS_CFG_TIMER_TASK_STK_SIZE;k_timer_task:软件定时器任务控制块k_timer_task_stk:软件定时器任务栈,其大小为TOS_CFG_TIMER_TASK_STK_SIZEk_timer_task_prio:软件定时器任务优先级,值为TOS_CFG_TIMER_TASK_PRIO,默认值是 (k_prio_t)(K_TASK_PRIO_IDLE - (k_prio_t)1u),比空闲任务高1个数值优先级,杰杰认为这也是很低的优先级了,这样一来软件定时器的精度将更低,不过好在这个值是可以被用户自定义的,想让精度高一点就将这个软件定时器任务优先级设置得高一点就好。k_timer_task_stk_addr:软件定时器任务栈起始地址。k_timer_task_stk_size:软件定时器任务栈大小。以下软件定时器的相关数据结构都在tos_timer.h中定义软件定时器的回调函数// 软件定时器的回调函数类型typedef void (*k_timer_callback_t)(void *arg);软件定时器的回调函数是一个函数指针的形式,它支持传入一个void指针类型的数据。 软件定时器控制块每个软件定时器都有对应的软件定时器控制块,每个软件定时器控制块都包含了软件定时器的基本信息,如软件定时器的状态、软件定时器工作模式、软件定时器的周期,剩余时间,以及软件定时器回调函数等信息。 typedef struct k_timer_st {#if TOS_CFG_OBJECT_VERIFY_EN > 0u knl_obj_t knl_obj; /**< 仅为了验证,测试当前内核对象是否真的是一个软件定时器 */#endif k_timer_callback_t cb; /**< 时间到时回调函数 */ void *cb_arg; /**< 回调函数中传入的参数 */ k_list_t list; /**< 挂载到软件定时器列表的节点 */ k_tick_t expires; /**< 距离软件定时器的到期时间到期还剩多少时间(单位为tick) */ k_tick_t delay; /**< 还剩多少时间运行第一个到期的软件定时器(的回调函数) */ k_tick_t period; /**< 软件定时器的周期 */ k_opt_t opt; /**< 软件定时器的工作模式选项,可以是单次模式TOS_OPT_TIMER_ONESHOT,也可以是周期模式TOS_OPT_TIMER_PERIODIC */ timer_state_t state; /**< 软件定时器的状态 */} k_timer_t;软件定时器的工作模式// if we just want the timer to run only once, this option should be passed to tos_timer_create.#define TOS_OPT_TIMER_ONESHOT (k_opt_t)(0x0001u)// if we want the timer run periodically, this option should be passed to tos_timer_create.#define TOS_OPT_TIMER_PERIODIC (k_opt_t)(0x0002u)TOS_OPT_TIMER_ONESHOT:单次模式,软件定时器在超时后,只会执行一次回调函数,它的状态将被设置为TIMER_STATE_COMPLETED,不再重新执行它的回调函数,当然,用户还是可以重新启动这个单次模式的软件定时器,它并未被删除。如果只希望计时器运行一次,则应将此选项传递给tos_timer_create()。TOS_OPT_TIMER_PERIODIC :周期模式 ,软件定时器在超时后,会执行对应的回调函数,同时根据软件定时器控制块中的period成员变量的值再重新插入软件定时器列表中,这个定时器会按照指定的定时时间循环执行(周期性执行)回调函数,直到用户将定时器删除。如果我们希望计时器周期运行,则应将此选项传递给tos_timer_create()。软件定时器的状态定时器状态有以下几种: ...

October 17, 2019 · 4 min · jiezi

TencentOS-tiny深度源码分析3队列

队列基本概念队列是一种常用于任务间通信的数据结构,队列可以在任务与任务间、中断和任务间传递消息,实现了任务接收来自其他任务或中断的不固定长度的消息,任务能够从队列里面读取消息,当队列中的消息是空时,读取消息的任务将被阻塞,用户还可以指定任务等待消息的时间timeout,在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列数据有效。当队列中有新消息时,被阻塞的任务会被唤醒并处理新消息;当等待的时间超过了指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转为就绪态,消息队列是一种异步的通信方式。 通过队列服务,任务或中断服务例程可以将一条或多条消息放入队列中。同样,一个或多个任务可以从队列中获得消息。当有多个消息发送到队列时,通常是将先进入队列的消息先传给任务,也就是说,任务先得到的是最先进入队列的消息,即先进先出原则(FIFO),其实TencentOS tiny暂时不支持后进先出原则LIFO操作队列,但是支持后进先出操作消息队列。 提示:TencentOS tiny 的队列不等同于消息队列,虽然队列的底层实现是依赖消息队列,但在TencentOS tiny中将它们分离开,这是两个概念,毕竟操作是不一样的。队列的阻塞机制举个简单的例子来理解操作系统中的阻塞机制: 假设你某天去餐厅吃饭,但是餐厅没菜了,那么你可能会有3个选择,你扭头就走,既然都没菜了,肯定换一家餐厅啊是吧。或者你会选择等一下,说不定老板去买菜了,一会就有菜了呢,就能吃饭。又或者,你觉得这家餐厅非常好吃,吃不到饭你就不走了,在这死等~同样的:假设有一个任务A对某个队列进行读操作的时候(出队),发现它此时是没有消息的,那么此时任务A有3个选择:第一个选择,任务A扭头就走,既然队列没有消息,那我也不等了,干其它事情去,这样子任务A不会进入阻塞态;第二个选择,任务A还是在这里等等吧,可能过一会队列就有消息,此时任务A会进入阻塞状态,在等待着消息的到来,而任务A的等待时间就由我们自己指定,当阻塞的这段时间中任务A等到了队列的消息,那么任务A就会从阻塞态变成就绪态;假如等待超时了,队列还没消息,那任务A就不等了,从阻塞态中唤醒;第三个选择,任务A死等,不等到消息就不走了,这样子任务A就会进入阻塞态,直到完成读取队列的消息。 队列实现的数据结构队列控制块TencentOS tiny 通过队列控制块操作队列,其数据类型为k_queue_t,队列控制块由多个元素组成,主要有 pend_obj_t 类型的pend_obj以及k_msg_queue_t 类型的msg_queue消息列表。其实整个队列的实现非常简单,主要靠msg_queue中的queue_head成员变量(这其实是一个消息列表(消息链表)),所有的消息都会被记录在这个消息列表中,当读取消息的时候,会从消息列表读取消息。 继承自内核对象的数据结构 在 kernelcoreincludetos_pend.h 的 35 行typedef struct pend_object_st { pend_type_t type; k_list_t list;} pend_obj_t;消息列表的数据类型(消息队列控制块),在 kernelcoreincludetos_msg.h 文件的第 13 行typedef struct k_msg_queue_st {#if TOS_CFG_OBJECT_VERIFY_EN > 0u knl_obj_t knl_obj;#endif k_list_t queue_head;} k_msg_queue_t;队列控制块,在 kernelcoreincludetos_queue.h 文件的第 6 行typedef struct k_queue_st { pend_obj_t pend_obj; k_msg_queue_t msg_queue;} k_queue_t;队列控制块示意图如下: 消息控制块除了上述的队列控制块外,还有消息队列控制块,这是因为TencentOS tiny中实现队列是依赖消息队列的,既然队列可以传递数据(消息),则必须存在一种可以存储消息的数据结构,我称之为消息控制块,消息控制块中记录了消息的存储地址msg_addr,以及消息的大小msg_size,此外还存在一个list成员变量,可以将消息挂载到队列的消息列表中。 消息控制块数据结构, 在 kernelcoreincludetos_msg.h 文件的第 7 行typedef struct k_message_st { k_list_t list; void *msg_addr; size_t msg_size;} k_msg_t; ...

October 17, 2019 · 3 min · jiezi

TencentOS-tiny深度源码分析4消息队列

消息队列在前一篇文章中【TencentOS tiny学习】源码分析(3)——队列我们描述了TencentOS tiny的队列实现,同时也点出了TencentOS tiny的队列是依赖于消息队列的,那么我们今天来看看消息队列的实现。 其实消息队列是TencentOS tiny的一个基础组件,作为队列的底层。所以在tos_config.h中会用以下宏定义: #if (TOS_CFG_QUEUE_EN > 0u)#define TOS_CFG_MSG_EN 1u#else#define TOS_CFG_MSG_EN 0u#endif系统消息池初始化在系统初始化(tos_knl_init())的时候,系统就会将消息池进行初始化,其中, msgpool_init()函数就是用来初始化消息池的,该函数的定义位于 tos_msg.c文件中,函数的实现主要是通过一个for循环,将消息池k_msg_pool[TOS_CFG_MSG_POOL_SIZE]的成员变量进行初始化,初始化对应的列表节点,并且将它挂载到空闲消息列表上k_msg_freelist初始化完成示意图:(假设只有3个消息) __KERNEL__ void msgpool_init(void){ uint32_t i; for (i = 0; i < TOS_CFG_MSG_POOL_SIZE; ++i) { tos_list_init(&k_msg_pool[i].list); tos_list_add(&k_msg_pool[i].list, &k_msg_freelist); }}__API__ k_err_t tos_knl_init(void){ ··· #if (TOS_CFG_MSG_EN) > 0 msgpool_init(); #endif ···}消息队列创建这个函数在队列创建中会被调用,当然他也可以自己作为用户API接口提供给用户使用,而非仅仅是内核API接口。这个函数的本质上就是初始化消息队列中的消息列表queue_head。初始化完成示意图: __API__ k_err_t tos_msg_queue_create(k_msg_queue_t *msg_queue){ TOS_PTR_SANITY_CHECK(msg_queue);#if TOS_CFG_OBJECT_VERIFY_EN > 0u knl_object_init(&msg_queue->knl_obj, KNL_OBJ_TYPE_MSG_QUEUE);#endif tos_list_init(&msg_queue->queue_head); return K_ERR_NONE;}消息队列销毁tos_msg_queue_destroy()函数用于销毁一个消息队列,当消息队列不在使用是可以将其销毁,销毁的本质其实是将消息队列控制块的内容进行清除,首先判断一下消息队列控制块的类型是KNL_OBJ_TYPE_MSG_QUEUE,这个函数只能销毁队列类型的控制块。然后调用tos_msg_queue_flush()函数将队列的消息列表的消息全部“清空”,“清空”的意思是将挂载到队列上的消息释放回消息池(如果消息队列的消息列表存在消息,使用msgpool_free()函数释放消息)。并且通过tos_list_init()函数将消息队列的消息列表进行初始化,knl_object_deinit()函数是为了确保消息队列已经被销毁,此时消息队列控制块的pend_obj成员变量中的type 属性标识为KNL_OBJ_TYPE_NONE。 但是有一点要注意,因为队列控制块的RAM是由编译器静态分配的,所以即使是销毁了队列,这个内存也是没办法释放的~__API__ k_err_t tos_msg_queue_destroy(k_msg_queue_t *msg_queue){ TOS_PTR_SANITY_CHECK(msg_queue);#if TOS_CFG_OBJECT_VERIFY_EN > 0u if (!knl_object_verify(&msg_queue->knl_obj, KNL_OBJ_TYPE_MSG_QUEUE)) { return K_ERR_OBJ_INVALID; }#endif tos_msg_queue_flush(msg_queue); tos_list_init(&msg_queue->queue_head);#if TOS_CFG_OBJECT_VERIFY_EN > 0u knl_object_deinit(&msg_queue->knl_obj);#endif return K_ERR_NONE;}__API__ void tos_msg_queue_flush(k_msg_queue_t *msg_queue){ TOS_CPU_CPSR_ALLOC(); k_list_t *curr, *next; TOS_CPU_INT_DISABLE(); TOS_LIST_FOR_EACH_SAFE(curr, next, &msg_queue->queue_head) { msgpool_free(TOS_LIST_ENTRY(curr, k_msg_t, list)); } TOS_CPU_INT_ENABLE();}从消息队列获取消息tos_msg_queue_get()函数用于从消息队列中获取消息,获取到的消息通过msg_addr参数返回,获取到消息的大小通过msg_size参数返回给用户,当获取成功是返回K_ERR_NONE,否则返回对应的错误代码。这个从消息队列中获取消息的函数是不会产生阻塞的,如果有消息则获取成功,否则就获取失败,它的实现过程如下:TOS_CFG_OBJECT_VERIFY_EN 宏定义使能了,就调用knl_object_verify()函数确保是从消息队列中获取消息,然后通过TOS_LIST_FIRST_ENTRY_OR_NULL判断一下是消息队列的消息列表否存在消息,如果不存在则返回K_ERR_MSG_QUEUE_EMPTY表示消息队列是空的,反正将获取成功,获取成功后需要使用msgpool_free()函数将消息释放回消息池。 ...

October 17, 2019 · 2 min · jiezi

TencentOS-tiny深度源码分析5信号量

信号量信号量(sem)在操作系统中是一种实现系统中任务与任务、任务与中断间同步或者临界资源互斥保护的机制。在多任务系统中,各任务之间常需要同步或互斥,信号量就可以为用户提供这方面的支持。 抽象来说,信号量是一个非负整数,每当信号量被获取(pend)时,该整数会减一,当该整数的值为 0 时,表示信号量处于无效状态,将无法被再次获取,所有试图获取它的任务将进入阻塞态。通常一个信号量是有计数值的,它的计数值可以用于系统资源计数(统计)。 一般来说信号量的值有两种: 0:表示没有积累下来的post信号量操作,且可能有任务阻塞在此信号量上。正值:表示有一个或多个post信号量操作。一般来说信号量多用于同步而非互斥,因为操作系统中会提供另一种互斥机制(互斥锁),互斥量的互斥作用更完善:互斥锁有优先级继承机制,而信号量则没有这个机制,此外互斥量还拥有所有者属性,我们会在后续讲解。 信号量也如队列一样,拥有阻塞机制。任务需要等待某个中断发生后,再去执行对应的处理,那么任务可以处于阻塞态等待信号量,直到中断发生后释放信号量后,该任务才被唤醒去执行对应的处理。在释放(post)信号量的时候能立即将等待的任务转变为就绪态,如果任务的优先级在就绪任务中是最高的,任务就能立即被运行,这就是操作系统中的“实时响应,实时处理”。在操作系统中使用信号量可以提高处理的效率。 信号量的数据结构信号量控制块TencentOS tiny 通过信号量控制块操作信号量,其数据类型为k_sem_t ,信号量控制块由多个元素组成,主要有 pend_obj_t 类型的pend_obj以及k_sem_cnt_t类型的count。而pend_obj有点类似于面向对象的继承,继承一些属性,里面有描述内核资源的类型(如信号量、队列、互斥量等,同时还有一个等待列表list)。而count则是一个简单的变量(它是16位的无符号整数),表示信号量的值。 typedef struct k_sem_st { pend_obj_t pend_obj; k_sem_cnt_t count;} k_sem_t;与信号量相关的宏定义在tos_config.h中,使能信号量的宏定义是TOS_CFG_SEM_EN #define TOS_CFG_SEM_EN 1u信号量实现TencentOS tiny 中实现信号量非常简单,核心代码仅仅只有125行,可以说是非常少了。 创建信号量系统中每个信号量都有对应的信号量控制块,信号量控制块中包含了信号量的所有信息,比如它的等待列表、它的资源类型,以及它的信号量值,那么可以想象一下,创建信号量的本质是不是就是对信号量控制块进行初始化呢?很显然就是这样子的。因为在后续对信号量的操作都是通过信号量控制块来操作的,如果控制块没有信息,那怎么能操作嘛~ 创建信号量函数是tos_sem_create(),传入两个参数,一个是信号量控制块的指针*sem,另一个是信号量的初始值init_count,该值是非负整数即可,但主要不能超过65535。 实际上就是调用pend_object_init()函数将信号量控制块中的sem->pend_obj成员变量进行初始化,它的资源类型被标识为PEND_TYPE_SEM。然后将sem->count成员变量设置为传递进来的信号量的初始值init_count。 __API__ k_err_t tos_sem_create(k_sem_t *sem, k_sem_cnt_t init_count){ TOS_PTR_SANITY_CHECK(sem); pend_object_init(&sem->pend_obj, PEND_TYPE_SEM); sem->count = init_count; return K_ERR_NONE;}销毁信号量信号量销毁函数是根据信号量控制块直接销毁的,销毁之后信号量的所有信息都会被清除,而且不能再次使用这个信号量,当信号量被销毁时,其等待列表中存在任务,系统有必要将这些等待这些任务唤醒,并告知任务信号量已经被销毁了PEND_STATE_DESTROY。然后产生一次任务调度以切换到最高优先级任务执行。 TencentOS tiny 对信号量销毁的处理流程如下: 调用pend_is_nopending()函数判断一下是否有任务在等待信号量如果有则调用pend_wakeup_all()函数将这些任务唤醒,并且告知等待任务信号量已经被销毁了(即设置任务控制块中的等待状态成员变量pend_state为PEND_STATE_DESTROY)。调用pend_object_deinit()函数将信号量控制块中的内容清除,最主要的是将控制块中的资源类型设置为PEND_TYPE_NONE,这样子就无法使用这个信号量了。进行任务调度knl_sched()注意:如果信号量控制块的RAM是由编译器静态分配的,所以即使是销毁了信号量,这个内存也是没办法释放的。当然你也可以使用动态内存为信号量控制块分配内存,只不过在销毁后要将这个内存释放掉,避免内存泄漏。 __API__ k_err_t tos_sem_destroy(k_sem_t *sem){ TOS_CPU_CPSR_ALLOC(); TOS_PTR_SANITY_CHECK(sem);#if TOS_CFG_OBJECT_VERIFY_EN > 0u if (!pend_object_verify(&sem->pend_obj, PEND_TYPE_SEM)) { return K_ERR_OBJ_INVALID; }#endif TOS_CPU_INT_DISABLE(); if (!pend_is_nopending(&sem->pend_obj)) { pend_wakeup_all(&sem->pend_obj, PEND_STATE_DESTROY); } pend_object_deinit(&sem->pend_obj); TOS_CPU_INT_ENABLE(); knl_sched(); return K_ERR_NONE;}获取信号量tos_sem_pend()函数用于获取信号量,当信号量有效的时候,任务才能获取信号量。任务获取了某个信号量时,该信号量的可用个数减一,当它为0的时候,获取信号量的任务会进入阻塞态,阻塞时间timeout由用户指定,在指定时间还无法获取到信号量时,将发送超时,等待任务将自动恢复为就绪态。 ...

October 17, 2019 · 2 min · jiezi

TencentOS-tiny深度源码分析6互斥锁

互斥锁互斥锁又称互斥互斥锁,是一种特殊的信号量,它和信号量不同的是,它具有互斥锁所有权、递归访问以及优先级继承等特性,在操作系统中常用于对临界资源的独占式处理。在任意时刻互斥锁的状态只有两种,开锁或闭锁,当互斥锁被任务持有时,该互斥锁处于闭锁状态,当该任务释放互斥锁时,该互斥锁处于开锁状态。 一个任务持有互斥锁就表示它拥有互斥锁的所有权,只有该任务才能释放互斥锁,同时其他任务将不能持有该互斥锁,这就是互斥锁的所有权特性。当持有互斥锁的任务再次获取互斥锁时不会被挂起,而是能递归获取,这就是互斥锁的递归访问特性。这个特性与一般的信号量有很大的不同,在信号量中,由于已经不存在可用的信号量,任务递归获取信号量时会发生挂起任务最终形成死锁。互斥锁还拥有优先级继承机制,它可以将低优先级任务的优先级临时提升到与获取互斥锁的高优先级任务的优先级相同,尽可能降低优先级翻转的危害。在实际应用中,如果想要实现同步功能,可以使用信号量,虽然互斥锁也可以用于任务与任务间的同步,但互斥锁更多的是用于临界资源的互斥访问。 使用互斥锁对临界资源保护时,任务必须先获取互斥锁以获得访问该资源的所有权,当任务使用资源后,必须释放互斥锁以便其他任务可以访问该资源(而使用信号量保护临界资源时则可能发生优先级翻转,且危害是不可控的)。 优先级翻转简单来说就是高优先级任务在等待低优先级任务执行完毕,这已经违背了操作系统的设计初衷(抢占式调度)。 为什么会发生优先级翻转? 当系统中某个临界资源受到一个互斥锁保护时,任务访问该资源时需要获得互斥锁,如果这个资源正在被一个低优先级任务使用,此时互斥锁处于闭锁状态;如果此时一个高优先级任务想要访问该资源,那么高优先级任务会因为获取不到互斥锁而进入阻塞态,此时就形成高优先级任务在等待低优先级任务运行(等待它释放互斥锁)。 优先级翻转是会产生危害的,在一开始设计系统的时候,就已经指定任务的优先级,越重要的任务优先级越高,但是发生优先级翻转后,高优先级任务在等待低优先级任务,这就有可能让高优先级任务得不到有效的处理,严重时可能导致系统崩溃。 优先级翻转的危害是不可控的,因为低优先级任务很可能会被系统中其他中间优先级任务(低优先级与高优先级任务之间的优先级任务)抢占,这就有可能导致高优先级任务将等待所有中间优先级任务运行完毕的情况,这种情况对高优先级任务来说是不可接受的,也是违背了操作系统设计的原则。 优先级继承在TencentOS tiny 中为了降低优先级翻转产生的危害使用了优先级继承算法:暂时提高占有某种临界资源的低优先级任务的优先级,使之与在所有等待该资源的任务中,优先级最高的任务优先级相等,而当这个低优先级任务执行完毕释放该资源时,优先级恢复初始设定值(此处可以看作是低优先级任务临时继承了高优先级任务的优先级)。因此,继承优先级的任务避免了系统资源被任何中间优先级任务抢占。互斥锁的优先级继承机制,它确保高优先级任务进入阻塞状态的时间尽可能短,以及将已经出现的“优先级翻转”危害降低到最小,但不能消除优先级翻转带来的危害。 值得一提的是TencentOS tiny 在持有互斥锁时还允许调用修改任务优先级的API接口。 互斥锁的数据结构互斥锁控制块TencentOS tiny 通过互斥锁控制块操作互斥锁,其数据类型为k_mutex_t,互斥锁控制块由多个元素组成。 pend_obj有点类似于面向对象的继承,继承一些属性,里面有描述内核资源的类型(如互斥锁、队列、互斥量等,同时还有一个等待列表list)。pend_nesting实际上是一个uint8_t类型的变量,记录互斥锁被获取的次数,如果pend_nesting为0则表示开锁状态,不为0则表示闭锁状态,且它的值记录了互斥量被获取的次数(拥有递归访问特性)*owner是任务控制块指针,记录当前互斥锁被哪个任务持有。owner_orig_prio变量记录了持有互斥锁任务的优先级,因为有可能发生优先级继承机制而临时改变任务的优先级。(拥有优先级继承机制)。owner_list是一个列表节点,当互斥锁被任务获取时,该节点会被添加到任务控制块的task->mutex_own_list列表中,表示任务自己获取到的互斥锁有哪些。当互斥锁被完全释放时(pend_nesting等于0),该节点将从任务控制块的task->mutex_own_list列表中移除。prio_pending变量比较有意思:在一般的操作系统中,任务在持有互斥锁时不允许修改优先级,但在TencentOS tiny 中是允许的,就是因为这个变量,当一个任务处于互斥锁优先级反转的时候,我假设他因为优先级反转优先级得到了提升,此时有用户企图改变这个任务的优先级,但这个更改后的优先级会使此任务违背其优先级必须比所有等待他所持有的互斥锁的任务还高的原则时,此时需要将用户的这次优先级更改请求记录到prio_pending里,待其释放其所持有的互斥锁后再更改,相当于将任务优先级的更改延后了。举个例子:好比一个任务优先级是10,且持有一把锁,此时一个优先级为6的任务尝试获取这把锁,那么此任务优先级会被提升为6,如果此时用户试图更改他的优先级为7,那么不能立刻响应这次请求,必须要等这把锁放掉的时候才能做真正的优先级修改typedef struct k_mutex_st { pend_obj_t pend_obj; k_nesting_t pend_nesting; k_task_t *owner; k_prio_t owner_orig_prio; k_list_t owner_list;} k_mutex_t;typedef struct k_task_st {#if TOS_CFG_MUTEX_EN > 0u k_list_t mutex_own_list; /**< 任务拥有的互斥量 */ k_prio_t prio_pending; /*< 在持有互斥锁时修改任务优先级将被记录到这个变量中,在释放持有的互斥锁时再更改 */#endif} k_task_t;与互斥锁相关的宏定义在tos_config.h中,使能互斥锁的宏定义是TOS_CFG_MUTEX_EN #define TOS_CFG_MUTEX_EN 1u创建互斥锁系统中每个互斥锁都有对应的互斥锁控制块,互斥锁控制块中包含了互斥锁的所有信息,比如它的等待列表、它的资源类型,以及它的互斥锁值,那么可以想象一下,创建互斥锁的本质是不是就是对互斥锁控制块进行初始化呢?很显然就是这样子的。因为在后续对互斥锁的操作都是通过互斥锁控制块来操作的,如果控制块没有信息,那怎么能操作嘛~ 创建互斥锁函数是tos_mutex_create(),传入一个互斥锁控制块的指针*mutex即可。 互斥锁的创建实际上就是调用pend_object_init()函数将互斥锁控制块中的mutex->pend_obj成员变量进行初始化,它的资源类型被标识为PEND_TYPE_MUTEX。然后将mutex->pend_nesting成员变量设置为0,表示互斥锁处于开锁状态;将mutex->owner成员变量设置为K_NULL,表示此事并无任务持有互斥锁;将mutex->owner_orig_prio成员变量设置为默认值K_TASK_PRIO_INVALID,毕竟此事没有任务持有互斥锁,也无需记录持有互斥锁任务的优先级。最终调用tos_list_init()函数将互斥锁的持有互斥锁任务的列表节点owner_list。 __API__ k_err_t tos_mutex_create(k_mutex_t *mutex){ TOS_PTR_SANITY_CHECK(mutex); pend_object_init(&mutex->pend_obj, PEND_TYPE_MUTEX); mutex->pend_nesting = (k_nesting_t)0u; mutex->owner = K_NULL; mutex->owner_orig_prio = K_TASK_PRIO_INVALID; tos_list_init(&mutex->owner_list); return K_ERR_NONE;}销毁互斥锁互斥锁销毁函数是根据互斥锁控制块直接销毁的,销毁之后互斥锁的所有信息都会被清除,而且不能再次使用这个互斥锁,当互斥锁被销毁时,其等待列表中存在任务,系统有必要将这些等待这些任务唤醒,并告知任务互斥锁已经被销毁了PEND_STATE_DESTROY。然后产生一次任务调度以切换到最高优先级任务执行。 ...

October 17, 2019 · 3 min · jiezi

TencentOS-tiny深度源码分析7事件

引言大家在裸机编程中很可能经常用到flag这种变量,用来标志一下某个事件的发生,然后在循环中判断这些标志是否发生,如果是等待多个事件的话,还可能会if((xxx_flag)&&(xxx_flag))这样子做判断。当然,如果聪明一点的同学就会拿flag的某些位做标志,比如这个变量的第一位表示A事件,第二位表示B事件,当这两个事件都发生的时候,就判断flag&0x03的值是多少,从而判断出哪个事件发生了。 但在操作系统中又将如何实现呢? 事件在操作系统中,事件是一种内核资源,主要用于任务与任务间、中断与任务间的同步,不提供数据传输功能! 与使用信号量同步有细微的差别:事件它可以实现一对多,多对多的同步。即一个任务可以等待多个事件的发生:可以是任意一个事件发生时唤醒任务进行事件处理;也可以是几个事件都发生后才唤醒任务进行事件处理。同样,也可以是多个任务同步多个事件。 每一个事件组只需要极少的RAM空间来保存事件旗标,一个事件(控制块)中包含了一个旗标,这个旗标的每一位表示一个“事件”,旗标存储在一个k_event_flag_t类型的变量中(名字叫flag,旗标简单理解就是事件标记变量),该变量在事件控制块中被定义,每一位代表一个事件,任务通过“逻辑与”或“逻辑或”与一个或多个事件建立关联,在事件发生时任务将被唤醒。 事件“逻辑或”是独立型同步,指的是任务所等待的若干事件中任意一个事件发生即可被唤醒; 事件“逻辑与”则是关联型同步,指的是任务所等待的若干事件中全部都发生时才被唤醒。事件是一种实现任务间通信的机制,可用于实现任务间的同步,但事件无数据传输。多任务环境下,任务、中断之间往往需要同步操作,一个事件发生会告知等待中的任务,即形成一个任务与任务、中断与任务间的同步。 事件无排队性,即多次向任务设置同一事件(如果任务还未来得及读走),等效于只设置一次。 此外事件可以提供一对多、多对多的同步操作。 一对多同步模型:一个任务等待多个事件的触发,这种情况是比较常见的;多对多同步模型:多个任务等待多个事件的触发,任务可以通过设置事件位来实现事件的触发和等待操作。事件数据结构事件控制块TencentOS tiny 通过事件控制块操作事件,其数据类型为k_event_t,事件控制块由多个元素组成。 pend_obj有点类似于面向对象的继承,继承一些属性,里面有描述内核资源的类型(如互斥锁、队列、互斥量等,同时还有一个等待列表list)。flag是旗标,一个32位的变量,因此每个事件控制块最多只能标识32个事件发生!typedef struct k_event_st { pend_obj_t pend_obj; k_event_flag_t flag;} k_event_t;任务控制块与事件相关的数据结构typedef struct k_task_st { ··· k_opt_t opt_event_pend; /**< 等待事件的的操作类型:TOS_OPT_EVENT_PEND_ANY 、 TOS_OPT_EVENT_PEND_ALL */ k_event_flag_t flag_expect; /**< 期待发生的事件 */ k_event_flag_t *flag_match; /**< 等待到的事件(匹配的事件) */ ···} k_task_t;与事件相关的宏定义在tos_config.h中,配置事件开关的宏定义是TOS_CFG_EVENT_EN #define TOS_CFG_EVENT_EN 1u在tos_event.h中,存在一些宏定义是用于操作事件的(opt选项): // if we are pending an event, for any flag we expect is set is ok, this flag should be passed to tos_event_pend #define TOS_OPT_EVENT_PEND_ANY (k_opt_t)0x0001// if we are pending an event, must all the flag we expect is set is ok, this flag should be passed to tos_event_pend #define TOS_OPT_EVENT_PEND_ALL (k_opt_t)0x0002#define TOS_OPT_EVENT_PEND_CLR (k_opt_t)0x0004TOS_OPT_EVENT_PEND_ANY:任务在等待任意一个事件发生,即“逻辑或”!TOS_OPT_EVENT_PEND_ALL:任务在等待所有事件发生,即“逻辑与”!TOS_OPT_EVENT_PEND_CLR:清除等待到的事件旗标,可以与TOS_OPT_EVENT_PEND_ANY、TOS_OPT_EVENT_PEND_ALL混合使用(通过“|”运算符)。除此之外还有一个枚举类型的数据结构,用于发送事件时的选项操作,可以在发送事件时清除事件旗标的其他位(即覆盖,影响其他事件),也可以保持原本旗标中的其他位(不覆盖,不影响其他事件)。 ...

October 17, 2019 · 3 min · jiezi

TencentOS-tiny深度源码分析2调度器

温馨提示:本文不描述与浮点相关的寄存器的内容,如需了解自行查阅(毕竟我自己也不懂)调度器的基本概念TencentOS tiny中提供的任务调度器是基于优先级的全抢占式调度,在系统运行过程中,当有比当前任务优先级更高的任务就绪时,当前任务将立刻被切出,高优先级任务抢占处理器运行。 TencentOS tiny内核中也允许创建相同优先级的任务。相同优先级的任务采用时间片轮转方式进行调度(也就是通常说的分时调度器),时间片轮转调度仅在当前系统中无更高优先级就绪任务的情况下才有效。 为了保证系统的实时性,系统尽最大可能地保证高优先级的任务得以运行。任务调度的原则是一旦任务状态发生了改变,并且当前运行的任务优先级小于优先级队列中任务最高优先级时,立刻进行任务切换(除非当前系统处于中断处理程序中或禁止任务切换的状态)。 调度器是操作系统的核心,其主要功能就是实现任务的切换,即从就绪列表里面找到优先级最高的任务,然后去执行该任务。 启动调度器调度器的启动由cpu_sched_start函数来完成,它会被tos_knl_start函数调用,这个函数中主要做两件事,首先通过readyqueue_highest_ready_task_get函数获取当前系统中处于最高优先级的就绪任务,并且将它赋值给指向当前任务控制块的指针k_curr_task ,然后设置一下系统的状态为运行态KNL_STATE_RUNNING。 当然最重要的是调用汇编代码写的函数cpu_sched_start启动调度器,该函数在源码的arch\arm\arm-v7m目录下的port_s.S汇编文件下,TencentOS tiny支持多种内核的芯片,如M3/M4/M7等,不同的芯片该函数的实现方式不同,port_s.S也是TencentOS tiny作为软件与CPU硬件连接的桥梁。以M4的cpu_sched_start举个例子: __API__ k_err_t tos_knl_start(void){ if (tos_knl_is_running()) { return K_ERR_KNL_RUNNING; } k_next_task = readyqueue_highest_ready_task_get(); k_curr_task = k_next_task; k_knl_state = KNL_STATE_RUNNING; cpu_sched_start(); return K_ERR_NONE;}port_sched_start CPSID I ; set pendsv priority lowest ; otherwise trigger pendsv in port_irq_context_switch will cause a context swich in irq ; that would be a disaster MOV32 R0, NVIC_SYSPRI14 MOV32 R1, NVIC_PENDSV_PRI STRB R1, [R0] LDR R0, =SCB_VTOR LDR R0, [R0] LDR R0, [R0] MSR MSP, R0 ; k_curr_task = k_next_task MOV32 R0, k_curr_task MOV32 R1, k_next_task LDR R2, [R1] STR R2, [R0] ; sp = k_next_task->sp LDR R0, [R2] ; PSP = sp MSR PSP, R0 ; using PSP MRS R0, CONTROL ORR R0, R0, #2 MSR CONTROL, R0 ISB ; restore r4-11 from new process stack LDMFD SP!, {R4 - R11} IF {FPU} != "SoftVFP" ; ignore EXC_RETURN the first switch LDMFD SP!, {R0} ENDIF ; restore r0, r3 LDMFD SP!, {R0 - R3} ; load R12 and LR LDMFD SP!, {R12, LR} ; load PC and discard xPSR LDMFD SP!, {R1, R2} CPSIE I BX R1Cortex-M内核关中断指令从上面的汇编代码,我又想介绍一下Cortex-M内核关中断指令,唉~感觉还是有点麻烦!为了快速地开关中断, Cortex-M内核专门设置了一条 CPS 指令,用于操作PRIMASK寄存器跟FAULTMASK寄存器的,这两个寄存器是与屏蔽中断有关的,除此之外Cortex-M内核还存在BASEPRI寄存器也是与中断有关的,也顺带介绍一下吧。 ...

October 17, 2019 · 5 min · jiezi

TencentOS-tiny深度源码分析1task

任务的基本概念从系统的角度看,任务是竞争系统资源的最小运行单元。TencentOS tiny是一个支持多任务的操作系统,任务可以使用或等待CPU、使用内存空间等系统资源,并独立于其它任务运行,理论上任何数量的任务都可以共享同一个优先级,这样子处于就绪态的多个相同优先级任务将会以时间片切换的方式共享处理器。 不过要注意的是:在TencentOS tiny中,不能创建与空闲任务相同优先级的任务K_TASK_PRIO_IDLE,相同优先级下的任务需要允许使用时间片调度,打开TOS_CFG_ROUND_ROBIN_EN。简而言之: TencentOS tiny的任务可认为是一系列独立任务的集合。每个任务在自己的环境中运行。在任何时刻,只有一个任务得到运行,由TencentOS tiny调度器决定运行哪个任务。从宏观看上去所有的任务都在同时在执行。 TencentOS中的任务是抢占式调度机制,高优先级的任务可打断低优先级任务,低优先级任务必须在高优先级任务阻塞或结束后才能得到调度。同时TencentOS也支持时间片轮转调度方式。 系统默认可以支持10个优先级,0~TOS_CFG_TASK_PRIO_MAX,这个宏定义是可以修改的,优先级数值越大的任务优先级越低,(TOS_CFG_TASK_PRIO_MAX - (k_prio_t)1u)为最低优先级,分配给空闲任务使用。 #define K_TASK_PRIO_IDLE (k_prio_t)(TOS_CFG_TASK_PRIO_MAX - (k_prio_t)1u)#define K_TASK_PRIO_INVALID (k_prio_t)(TOS_CFG_TASK_PRIO_MAX)任务状态TencentOS tiny任务状态有以下几种。 就绪态(K_TASK_STATE_READY):该任务在就绪列表中,就绪的任务已经具备执行的能力,只等待调度器进行调度,新创建的任务会初始化为就绪态。运行态(K_TASK_STATE_READY):该状态表明任务正在执行,此时它占用处理器,其实此时的任务还是处于就绪列表中的,TencentOS调度器选择运行的永远是处于最高优先级的就绪态任务,当任务被运行的一刻,它的任务状态就变成了运行态。睡眠态(K_TASK_STATE_SLEEP):如果任务当前正在休眠让出CPU使用权,那么就可以说这个任务处于休眠状态,该任务不在就绪列表中,此时任务处于睡眠列表中(或者叫延时列表)。等待态(K_TASK_STATE_PEND):任务正在等待信号量、队列或者等待事件等状态。挂起态(K_TASK_STATE_SUSPENDED):任务被挂起,此时任务对调度器而言是不可见的。退出态(K_TASK_STATE_DELETED):该任务运行结束,并且被删除。等待超时状态(K_TASK_STATE_PENDTIMEOUT):任务正在等待信号量、队列或者等待事件发生超时的状态。睡眠挂起态(K_TASK_STATE_SLEEP_SUSPENDED):任务在睡眠中被挂起时的状态。等待挂起态(K_TASK_STATE_PEND_SUSPENDED):任务正在等待信号量、队列或者等待事件时被挂起的状态。等待超时挂起态(K_TASK_STATE_PENDTIMEOUT_SUSPENDED):任务正在等待信号量、队列或者等待事件发生超时,但此时任务已经被挂起的状态。// ready to schedule// a task's pend_list is in readyqueue#define K_TASK_STATE_READY (k_task_state_t)0x0000// delayed, or pend for a timeout// a task's tick_list is in k_tick_list#define K_TASK_STATE_SLEEP (k_task_state_t)0x0001// pend for something// a task's pend_list is in some pend object's list#define K_TASK_STATE_PEND (k_task_state_t)0x0002// suspended#define K_TASK_STATE_SUSPENDED (k_task_state_t)0x0004// deleted#define K_TASK_STATE_DELETED (k_task_state_t)0x0008// actually we don't really need those TASK_STATE below, if you understand the task state deeply, the code can be much more elegant. // we are pending, also we are waitting for a timeout(eg. tos_sem_pend with a valid timeout, not TOS_TIME_FOREVER)// both a task's tick_list and pend_list is not empty#define K_TASK_STATE_PENDTIMEOUT (k_task_state_t)(K_TASK_STATE_PEND | K_TASK_STATE_SLEEP)// suspended when sleeping#define K_TASK_STATE_SLEEP_SUSPENDED (k_task_state_t)(K_TASK_STATE_SLEEP | K_TASK_STATE_SUSPENDED)// suspened when pending#define K_TASK_STATE_PEND_SUSPENDED (k_task_state_t)(K_TASK_STATE_PEND | K_TASK_STATE_SUSPENDED)// suspended when pendtimeout#define K_TASK_STATE_PENDTIMEOUT_SUSPENDED (k_task_state_t)(K_TASK_STATE_PENDTIMEOUT | K_TASK_STATE_SUSPENDED)TencentOS中维护任务的数据结构就绪列表TencentOS tiny维护一条就绪列表,用于挂载系统中的所有处于就绪态的任务,他是readyqueue_t 类型的列表,其成员变量如下: ...

October 17, 2019 · 3 min · jiezi

新南威尔士大学-COMP1511-Assignment1-课业解析

新南威尔士大学 COMP1511 Assignment1 课业解析题意: 使用C语言开发一款画图软件,允许用户在终端输入一串命令在画布上画画,输出一个存储像素信息的数组 解析: 用一个int型2维数组代表画布,每个元素代表电子画布的像素,二维数组初始化时值为4(4代表white,3代表light,2代表grey,1代表dark,0代表black);程序读取用户指令(一串int型数字,Ctrl+D结束输入),处理并输出数组的值。如指令draw line——画一条线段,用户输入1 5 5 5 180代表画一条线段,1代表画线段,起始点是(5,5),方向向下,长度为5个像素。 第一阶段要求实现画线段(水平和竖直方向)和正方形功能,画正方形例如2 6 6 4 0,2表示填充一个正方形,以(6,6)为起始点沿向上方向延伸4个像素为正方形对角线,填充该正方形区域;第二阶段要求实现绘制对角线方向的线段以及改变像素点颜色;第三阶段要求实现复制粘贴选定正方形区域的像素内容并粘贴到目标区域,例如4 0 0 3 135 0 10,4代表复制粘贴命令,从(0,0)开始,沿右下方延伸3个像素点,复制以此为对角线的正方形区域像素,并粘贴到以(0,10)为起始点的正方形区域上。 第三、四阶段要求实现绘制椭圆,例如0 0 0 3 3 5.5 1,0表示绘制椭圆,第一个焦点是(0,0),第二个焦点是(3,3),动点P到两焦点的距离和为2*5.5=11,绘制动点P的运动轨迹形成的椭圆,1表示填充椭圆内部所有像素,若为0则只画轮廓。 涉及知识点: 数组、类、while 更多可+讨论 唯心:g19963812037 pdf 2019/10/9 Assignment 1 - CS Painthttps://cgi.cse.unsw.edu.au/~... 1/30COMP1511 19T3 Assignment 1 - CS Paint COMP1511 19T3version: 1.1 last updated: 2019-10-09 14:00CS Paint²The year is 1985 . . . Microsoft has just released Windows 1.0 and packaged with it is a beautiful program called Paint, later referred toas MS Paint. For many people, this program is the beginning of a wonderful journey into the world of digital art.In this assignment, you will be implementing CS Paint, COMP1511's answer to the venerable drawing program. CS Paint is a programthat allows us to draw images to our terminal using a series of commands. The commands are made up of integers (and in later stages,doubles) and are typed directly into our program. Each command will make some change to a digital canvas, a space for drawing.CS Paint is already capable of setting up and drawing its canvas, it will be up to you to write code so that it can read commands andmake the correct changes in the canvas.Note: At time of release of this assignment (end of Week 3), COMP1511 has not yet covered all of the techniques and topics necessaryto complete this assignment. At the end of Week 3, the course has covered enough content to be able to read in a single command andprocess its integers, but not enough to work with two dimensional arrays like the canvas or be able to handle multiple commandsending in End-of-Input (Ctrl-D). We will be covering these topics in the lectures, tutorials and labs of Week 4.2019/10/9 Assignment 1 - CS Painthttps://cgi.cse.unsw.edu.au/~... 2/30The CanvasThe canvas is a two dimensional array (an array of arrays) of integers that represents the space we will be drawing in. We will bereferring to individual elements of these arrays as pixels on the canvas.The canvas is a fixed size and has N_ROWS rows, and N_COLS columns. Both of these are defined constants.Both the rows and columns start at 0, not at 1.The top left corner of the canvas is (0, 0) and the bottom right corner of the canvas is (N_ROWS - 1, N_COLS - 1). Note that weare using rows as the first coordinate in pairs of coordinates.For example, if we are given an input coordinate 5 10, we will use that to find a particular cell in our canvas by accessing the individualelement in the array: canvas5The integers in the pixels represent colours between black (which we call 0) and white (which we call 4). We will be starting with awhite canvas and drawing black onto it, but as we progress, we will also be using shades of grey (not 50 of them, just a few). Note thatthese colours assume you have white text on a black background.For reference, the shades are:Black (0):Dark (1): ░░Grey (2): ▒▒Light (3): ▓▓White (4): ██An empty canvas is shown below. In this documentation, we will always show you two versions of the output. In the "Output" you cansee the version that your program is expected to produce (numbers between 0 and 4).In the "Output (Stylized)" tab you can see a more readable version with the numbers converted to shades.Note that you are not expected to produce this stylized output - we have tools that will convert it for you. Your program only needs toprint the grid of numbers, as shown in the "Output" tab.2019/10/9 Assignment 1 - CS Painthttps://cgi.cse.unsw.edu.au/~... 3/30Empty Canvas ...

October 16, 2019 · 30 min · jiezi

从单片机到操作系统⑦深入了解FreeRTOS的延时机制

没研究过操作系统的源码都不算学过操作系统FreeRTOS 时间管理时间管理包括两个方面:系统节拍以及任务延时管理。 系统节拍:在前面的文章也讲得很多,想要系统正常运行,那么时钟节拍是必不可少的,FreeRTOS的时钟节拍通常由SysTick提供,它周期性的产生定时中断,所谓的时钟节拍管理的核心就是这个定时中断的服务程序。FreeRTOS的时钟节拍isr中核心的工作就是调用vTaskIncrementTick()函数。具体见上之前的文章。 延时管理FreeRTOS提供了两个系统延时函数: 相对延时函数vTaskDelay() 绝对延时函数vTaskDelayUntil()。这些延时函数可不像我们以前用裸机写代码的延时函数操作系统不允许CPU在死等消耗着时间,因为这样效率太低了。 同时,要告诫学操作系统的同学,千万别用裸机的思想去学操作系统。 任务延时任务可能需要延时,两种情况,一种是任务被vTaskDelay或者vTaskDelayUntil延时,另外一种情况就是任务等待事件(比如等待某个信号量、或者某个消息队列)时候指定了timeout(即最多等待timeout时间,如果等待的事件还没发生,则不再继续等待),在每个任务的循环中都必须要有阻塞的情况出现,否则比该任务优先级低的任务就永远无法运行。 相对延时与绝对延时的区别 相对延时:vTaskDelay(): 相对延时是指每次延时都是从任务执行函数vTaskDelay()开始,延时指定的时间结束 绝对延时:vTaskDelayUntil(): 绝对延时是指调用vTaskDelayUntil()的任务每隔x时间运行一次。也就是任务周期运行。 相对延时:vTaskDelay() 相对延时vTaskDelay()是从调用vTaskDelay()这个函数的时候开始延时,但是任务执行的时候,可能发生了中断,导致任务执行时间变长了,但是整个任务的延时时间还是1000个tick,这就不是周期性了,简单看看下面代码: void vTaskA( void * pvParameters ) { while(1) { // ... // 这里为任务主体代码 // ... /* 调用相对延时函数,阻塞1000个tick */ vTaskDelay( 1000 ); } } 可能说的不够明确,可以看看图解。 当任务运行的时候,假设被某个高级任务或者是中断打断了,那么任务的执行时间就更长了,然而延时还是延时1000个tick这样子,整个系统的时间就混乱了。 如果还不够明确,看看vTaskDelay()的源码 void vTaskDelay( const TickType_t xTicksToDelay ){ BaseType_t xAlreadyYielded = pdFALSE; /* 延迟时间为零只会强制切换任务。 */ if( xTicksToDelay > ( TickType_t ) 0U ) (1) { configASSERT( uxSchedulerSuspended == 0 ); vTaskSuspendAll(); (2) { traceTASK_DELAY(); /*将当前任务从就绪列表中移除,并根据当前系统节拍 计数器值计算唤醒时间,然后将任务加入延时列表 */ prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE ); } xAlreadyYielded = xTaskResumeAll(); } else { mtCOVERAGE_TEST_MARKER(); } /* 强制执行一次上下文切换 */ if( xAlreadyYielded == pdFALSE ) { portYIELD_WITHIN_API(); } else { mtCOVERAGE_TEST_MARKER(); }}(1):如果传递进来的延时时间是0,只能进行强制切换任务了,调用的是portYIELD_WITHIN_API(),它其实是一个宏,真正起作用的是portYIELD(),下面是它的源码:#define portYIELD() \{ \ /* 设置PendSV以请求上下文切换。 */ \ portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \ __dsb( portSY_FULL_READ_WRITE ); \ __isb( portSY_FULL_READ_WRITE ); \}(2):挂起当前任务然后将当前任务从就绪列表删除,然后加入到延时列表。是调用函数prvAddCurrentTaskToDelayedList()完成这一过程的。由于这个函数篇幅过长,就不讲解了,有兴趣可以看看,我就简单说说过程。在FreeRTOS中有这么一个变量,是用来记录systick的值的。 ...

October 15, 2019 · 1 min · jiezi

继续学习freertos消息队列

写在前面:杰杰这个月很忙~所以并没有时间更新,现在健身房闭馆装修,晚上有空就更新一下!其实在公众号没更新的这段日子,每天都有兄弟在来关注我的公众号,这让我受宠若惊,在这里谢谢大家的支持啦!!谢谢^在这里我们就跟着火哥的书来学习一下FreeRTOS的消息队列,这本书我觉得写得很好,基本都讲解到了,关于什么是消息队列,就请大家去看书,基础知识我暂时不说了。 声明:本书绝大部分内容来自《FreeRTOS 内核实现与应用开发实战指南—基于野火 STM32 全系列(M3/4/7)开发板》,如涉及侵权请联系杰杰删除 FreeRTOS的消息队列支持FreeRTOS 中使用队列数据结构实现任务异步通信工作,具有如下特性:消息支持先进先出方式排队,支持异步读写工作方式。读写队列均支持超时机制。消息支持后进先出方式排队, 往队首发送消息(LIFO) 。可以允许不同长度(不超过队列节点最大值)的任意类型消息。一个任务能够从任意一个消息队列接收和发送消息。多个任务能够从同一个消息队列接收和发送消息。当队列使用结束后,可以通过删除队列函数进行删除。FreeRTOS队列的特点一般来说,鱼与熊掌不可兼得,如果数据太多,那数据传输的速度必然是会慢下来,而如果采用引用传递的方式,当原始数据被修改的时候,数据有变得不安全,但是FreeRTOS支持拷贝与引用的方式进行数据的传输,变得更加灵活。队列是通过拷贝传递数据的,但这并不妨碍队列通过引用来传递数据。当信息的大小到达一个临界点后,逐字节拷贝整个信息是不实际的,可以定义一个指向数据区域的指针,将指针传递即可。这种方法在物联网中是非常常用的。 消息队列控制块其实消息队列不仅仅是用于当做消息队列,FreeRTOS还把他当做信号量的数据结构来使用 typedef struct QueueDefinition{ int8_t *pcHead; /* 指向队列存储区起始位置,即第一个队列项 */ int8_t *pcTail; /* 指向队列存储区结束后的下一个字节 */ int8_t *pcWriteTo; /* 指向下队列存储区的下一个空闲位置 */ union /* 使用联合体用来确保两个互斥的结构体成员不会同时出现 */ { int8_t *pcReadFrom; /* 当结构体用于队列时,这个字段指向出队项目中的最后一个. */ UBaseType_t uxRecursiveCallCount;/* 当结构体用于互斥量时,用作计数器,保存递归互斥量被"获取"的次数. */ } u; List_t xTasksWaitingToSend; /* 因为等待入队而阻塞的任务列表,按照优先级顺序存储 */ List_t xTasksWaitingToReceive; /* 因为等待队列项而阻塞的任务列表,按照优先级顺序存储 */ volatile UBaseType_t uxMessagesWaiting;/*< 当前队列的队列项数目 */ UBaseType_t uxLength; /* 队列项的数目 */ UBaseType_t uxItemSize; /* 每个队列项的大小 */ volatile BaseType_t xRxLock; /* 队列上锁后,存储从队列收到的列表项数目,如果队列没有上锁,设置为queueUNLOCKED */ volatile BaseType_t xTxLock; /* 队列上锁后,存储发送到队列的列表项数目,如果队列没有上锁,设置为queueUNLOCKED */ /* 删除部分源码 */} xQUEUE; typedef xQUEUE Queue_t;先过一遍消息队列的数据结构,其实没啥东西的,记不住也没啥大问题,下面会用到就行了。 ...

October 15, 2019 · 6 min · jiezi

FreeRTOS优化与错误排查方法

写在前面主要是为刚接触 FreeRTOS 的用户指出那些新手通常容易遇到的问题。这里把最主要的篇幅放在栈溢出以及栈溢出j检测上,因为栈相关的问题是初学者遇到最多的问题。 printf-stdarg.c当调用 C 标准库 的函数时,栈空间使用量可能会急剧上升,特别是 IO 与字符串处理函数,比如 sprintf()、printf()等。在 FreeRTOS 源码包中有一个名为 printf-stdarg.c 的文件。这个文件实现了一个栈效率优化版的小型 sprintf()、printf(),可以用来代替标准 C 库函数版本。在大多数情况下,这样做可以使得调用 sprintf()及相关函数的任务对栈空间的需求量小很多。可能很多人都不知道freertos中有这样子的一个文件,它放在第三方资料中,路径为“FreeRTOSv9.0.0FreeRTOS-PlusDemoFreeRTOS_Plus_UDP_and_CLI_LPC1830_GCC”,我们发布工程的时候就无需依赖 C 标准库,这样子就能减少栈的使用,能优化不少空间。该文件源码(部分): static int print( char **out, const char *format, va_list args ){ register int width, pad; register int pc = 0; char scr[2]; for (; *format != 0; ++format) { if (*format == '%') { ++format; width = pad = 0; if (*format == '\0') break; if (*format == '%') goto out; if (*format == '-') { ++format; pad = PAD_RIGHT; } while (*format == '0') { ++format; pad |= PAD_ZERO; } for ( ; *format >= '0' && *format <= '9'; ++format) { width *= 10; width += *format - '0'; } if( *format == 's' ) { register char *s = (char *)va_arg( args, int ); pc += prints (out, s?s:"(null)", width, pad); continue; } if( *format == 'd' || *format == 'i' ) { pc += printi (out, va_arg( args, int ), 10, 1, width, pad, 'a'); continue; } if( *format == 'x' ) { pc += printi (out, va_arg( args, int ), 16, 0, width, pad, 'a'); continue; } if( *format == 'X' ) { pc += printi (out, va_arg( args, int ), 16, 0, width, pad, 'A'); continue; } if( *format == 'u' ) { pc += printi (out, va_arg( args, int ), 10, 0, width, pad, 'a'); continue; } if( *format == 'c' ) { /* char are converted to int then pushed on the stack */ scr[0] = (char)va_arg( args, int ); scr[1] = '\0'; pc += prints (out, scr, width, pad); continue; } } else { out: printchar (out, *format); ++pc; } } if (out) **out = '\0'; va_end( args ); return pc;}int printf(const char *format, ...){ va_list args; va_start( args, format ); return print( 0, format, args );}int sprintf(char *out, const char *format, ...){ va_list args; va_start( args, format ); return print( &out, format, args );}int snprintf( char *buf, unsigned int count, const char *format, ... ){ va_list args; ( void ) count; va_start( args, format ); return print( &buf, format, args );}使用的例子与 C 标准库基本一样: ...

October 15, 2019 · 3 min · jiezi

从0开始学FreeRTOS任务调度4

大家晚上好,我是杰杰,最近挺忙的,好久没有更新了,今天周末就吐血更新一下吧! 前言FreeRTOS是一个是实时内核,任务是程序执行的最小单位,也是调度器处理的基本单位,移植了FreeRTOS,则避免不了对任务的管理,在多个任务运行的时候,任务切换显得尤为重要。而任务切换的效率会决定了系统的稳定性与效率。 FreeRTOS的任务切换是干嘛的呢,rtos的实际是永远运行的是具有最高优先级的运行态任务,而那些之前在就绪态的任务怎么变成运行态使其得以运行呢,这就是我们FreeRTOS任务切换要做的事情,它要做的是找到最高优先级的就绪态任务,并且让它获得cpu的使用权,这样,它就能从就绪态变成运行态,这样子,整个系统的实时性就会很好,响应也会很好,而不会让程序阻塞卡死。 要知道怎么实现任务切换,那就要知道任务切换的机制,在不同的cpu(mcu)中,触发的方式可能会不一样,现在是以Cortex-M3为例来讲讲任务的切换。为了大家能看懂本文,我就抛转引玉一下,引用《Cortex-M3权威指南-中文版》的部分语句(如涉及侵权,请联系杰杰删除) SVC 和 PendSVSVC(系统服务调用,亦简称系统调用)和 PendSV(Pended System Call,可悬起系统调用),它们多用于在操作系统之上的软件开发中。SVC 用于产生系统函数的调用请求。例如,操作系统不让用户程序直接访问硬件,而是通过提供一些系统服务函数,用户程序使用 SVC 发出对系统服务函数的呼叫请求,以这种方法调用它们来间接访问硬件。因此,当用户程序想要控制特定的硬件时,它就会产生一个 SVC 异常,然后操作系统提供的 SVC 异常服务例程得到执行,它再调用相关的操作系统函数,后者完成用户程序请求的服务。 另一个相关的异常是 PendSV(可悬起的系统调用),它和 SVC 协同使用。一方面,SVC异常是必须立即得到响应的(若因优先级不比当前正处理的高,或是其它原因使之无法立即响应,将上访成硬 fault——译者注),应用程序执行 SVC 时都是希望所需的请求立即得到响应。另一方面,PendSV 则不同,它是可以像普通的中断一样被悬起的(不像 SVC 那样会上访)。OS 可以利用它“缓期执行”一个异常——直到其它重要的任务完成后才执行动作。悬起 PendSV 的方法是:手工往 NVIC 的 PendSV 悬起寄存器中写 1。悬起后,如果优先级不够高,则将缓期等待执行。 如果一个发生的异常不能被即刻响应,就称它被“悬起”(pending)。不过,少数 fault异常是不允许被悬起的。一个异常被悬起的原因,可能是系统当前正在执行一个更高优先级异常的服务例程,或者因相关掩蔽位的设置导致该异常被除能。对于每个异常源,在被悬起的情况下,都会有一个对应的“悬起状态寄存器”保存其异常请求,直到该异常能够执行为止,这与传统的 ARM 是完全不同的。在以前,是由产生中断的设备保持住请求信号。现在NVIC 的悬起状态寄存器的出现解决了这个问题,即使后来设备已经释放了请求信号,曾经的中断请求也不会错失。 系统任务切换的工程分析在系统中正常执行的任务(假设没有外部中断IRQ),用Systick直接做上下文切换是完全没有问题的,如图: 但是问题是几乎很少嵌入式的设备会不用其丰富的中断响应,所以,直接用systick做系统的上下文切换那是不实际的,这存在很大的风险,因为假设systick打断了一个中断(IRQ),立即做出上下文切换的话,则触犯用法 fault 异常,除了重启你没有其他办法了,这样子做出来的产品就是垃圾!!用我老板的话说就是写的什么狗屎!!!如图所示: 那这么说这样不行那也不行,怎么办啊?请看看前面接介绍的PendSV,是不是有点豁然开朗了?PendSV 来完美解决这个问题。PendSV 异常会自动延迟上下文切换的请求,直到其它的 ISR 都完成了处理后才放行。为实现这个机制,需要把 PendSV 编程为最低优先级的异常。如果 OS 检测到某 IRQ 正在活动并且被 SysTick 抢占,它将悬起一个 PendSV 异常,以便缓期执行上下文切换。 懂了吗?就是说,只要将PendSV的优先级设为最低的,systick即使是打断了IRQ,它也不会马上进行上下文切换,而是等到IRQ执行完,PendSV 服务例程才开始执行,并且在里面执行上下文切换。过程如图所示: 任务切换的源码实现过程差不多了解了,那看看FreeRTOS中怎么实现吧!! FreeRTOS有两种方法触发任务切换: 一种就是systick触发PendSV异常,这是最经常使用的。另一种是主动进行切换任务,执行系统调用,比如普通任务可以使用taskYIELD()强制任务切换,中断服务程序中使用portYIELD_FROM_ISR()强制任务切换。第一种先说说第一种吧,就在systick中断中调用xPortSysTickHandler(); 下面是源码: void xPortSysTickHandler( void ){    vPortRaiseBASEPRI();    {        /* Increment the RTOS tick. */        if( xTaskIncrementTick() != pdFALSE )        {            /* A context switch is required.  Context switching is performed in            the PendSV interrupt.  Pend the PendSV interrupt. */            portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;        }    }    vPortClearBASEPRIFromISR();}它的执行过程是这样子的,屏蔽所有中断,因为SysTick以最低的中断优先级运行,所以当这个中断执行时所有中断必须被屏蔽。vPortRaiseBASEPRI();就是屏蔽所有中断的。而且并不需要保存本次中断的值,因为systick的中断优先级是已知的,执行完直接恢复所有中断即可。 ...

October 15, 2019 · 1 min · jiezi

超详细的FreeRTOS移植全教程基于srm32

准备在移植之前,我们首先要获取到FreeRTOS的官方的源码包。这里我们提供两个下载链接: 一个是官网:http://www.freertos.org/另外一个是代码托管网站:https://sourceforge.net/proje...这里我们演示如何在代码托管网站里面下载。打开网站链接之后,我们选择FreeRTOS的最新版本V9.0.0(2016年),尽管现在FreeRTOS的版本已经更新到V10.0.1了,但是我们还是选择V9.0.0,因为内核很稳定,并且网上资料很多,因为V10.0.0版本之后是亚马逊收购了FreeRTOS之后才出来的版本,主要添加了一些云端组件,我们本书所讲的FreeRTOS是实时内核,采用V9.0.0版本足以。 简单介绍FreeRTOSFreeRTOS包含Demo例程和内核源码(比较重要,我们就需要提取该目录下的大部分文件)。Source文件夹里面包含的是FreeRTOS内核的源代码,我们移植FreeRTOS的时候就需要这部分源代码;Demo 文件夹里面包含了FreeRTOS官方为各个单片机移植好的工程代码,FreeRTOS为了推广自己,会给各种半导体厂商的评估板写好完整的工程程序,这些程序就放在Demo这个目录下,这部分Demo非常有参考价值。 Source文件夹这里我们再重点分析下FreeRTOS/ Source文件夹下的文件,①和③包含的是FreeRTOS的通用的头文件和C文件,这两部分的文件试用于各种编译器和处理器,是通用的。需要移植的头文件和C文件放在②portblle这个文件夹。 portblle文件夹,是与编译器相关的文件夹,在不同的编译器中使用不同的支持文件。①中的KEIL就是我们就是我们使用的编译器,其实KEIL里面的内容跟RVDS里面的内容一样,所以我们只需要③RVDS文件夹里面的内容即可,里面包含了各种处理器相关的文件夹,从文件夹的名字我们就非常熟悉了,我们学习的STM32有M0、M3、M4等各种系列,FreeRTOS是一个软件,单片机是一个硬件,FreeRTOS要想运行在一个单片机上面,它们就必须关联在一起。MemMang文件夹下存放的是跟内存管理相关的源文件。 移植过程提取源码首先在我们的STM32裸机工程模板根目录下新建一个文件夹,命名为“FreeRTOS”,并且在FreeRTOS文件夹下新建两个空文件夹,分别命名为“src”与“port”,src文件夹用于保存FreeRTOS中的核心源文件,也就是我们常说的‘.c文件’,port文件夹用于保存内存管理以及处理器架构相关代码,这些代码FreeRTOS官方已经提供给我们的,直接使用即可,在前面已经说了,FreeRTOS是软件,我们的开发版是硬件,软硬件必须有桥梁来连接,这些与处理器架构相关的代码,可以称之为RTOS硬件接口层,它们位于FreeRTOS/Source/Portable文件夹下。打开FreeRTOS V9.0.0源码,在“FreeRTOSv9.0.0FreeRTOSSource”目录下找到所有的‘.c文件’,将它们拷贝到我们新建的src文件夹中, 打开FreeRTOS V9.0.0源码,在“FreeRTOSv9.0.0FreeRTOSSourceportable”目录下找到“MemMang”文件夹与“RVDS”文件夹,将它们拷贝到我们新建的port文件夹中 打开FreeRTOS V9.0.0源码,在“FreeRTOSv9.0.0 FreeRTOSSource”目录下找到“include”文件夹,它是我们需要用到FreeRTOS的一些头文件,将它直接拷贝到我们新建的FreeRTOS文件夹中,完成这一步之后就可以看到我们新建的FreeRTOS文件夹已经有3个文件夹,这3个文件夹就包含FreeRTOS的核心文件,至此,FreeRTOS的源码就提取完成。 添加到工程添加FreeRTOSConfig.h文件FreeRTOSConfig.h文件是FreeRTOS的工程配置文件,因为FreeRTOS是可以裁剪的实时操作内核,应用于不同的处理器平台,用户可以通过修改这个FreeRTOS内核的配置头文件来裁剪FreeRTOS的功能,所以我们把它拷贝一份放在user这个文件夹下面。打开FreeRTOSv9.0.0源码,在“FreeRTOSv9.0.0FreeRTOSDemo”文件夹下面找到“CORTEX_STM32F103_Keil”这个文件夹,双击打开,在其根目录下找到这个“FreeRTOSConfig.h”文件,然后拷贝到我们工程的user文件夹下即可,等下我们需要对这个文件进行修改。 创建工程分组接下来我们在mdk里面新建FreeRTOS/src和FreeRTOS/port两个组文件夹,其中FreeRTOS/src用于存放src文件夹的内容,FreeRTOS/port用于存放portMemMang文件夹 与portRVDSARM_CM3文件夹的内容。然后我们将工程文件中FreeRTOS的内容添加到工程中去,按照已经新建的分组添加我们的FreeRTOS工程源码。在FreeRTOS/port分组中添加MemMang文件夹中的文件只需选择其中一个即可,我们选择“heap_4.c”,这是FreeRTOS的一个内存管理源码文件。添加完成后: 添加头文件路径FreeRTOS的源码已经添加到开发环境的组文件夹下面,编译的时候需要为这些源文件指定头文件的路径,不然编译会报错。FreeRTOS的源码里面只有FreeRTOSinclude和FreeRTOSportRVDSARM_CM3这两个文件夹下面有头文件,只需要将这两个头文件的路径在开发环境里面指定即可。同时我们还将FreeRTOSConfig.h这个头文件拷贝到了工程根目录下的user文件夹下,所以user的路径也要加到开发环境里面。 修改FreeRTOSConfig.hFreeRTOSConfig.h是直接从demo文件夹下面拷贝过来的,该头文件对裁剪整个FreeRTOS所需的功能的宏均做了定义,有些宏定义被使能,有些宏定义被失能,一开始我们只需要配置最简单的功能即可。要想随心所欲的配置FreeRTOS的功能,我们必须对这些宏定义的功能有所掌握,下面我们先简单的介绍下这些宏定义的含义,然后再对这些宏定义进行修改。 #ifndef FREERTOS_CONFIG_H#define FREERTOS_CONFIG_H#include "stm32f10x.h"#include "bsp_usart.h"//针对不同的编译器调用不同的stdint.h文件#if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__) #include <stdint.h> extern uint32_t SystemCoreClock;#endif//断言#define vAssertCalled(char,int) printf("Error:%s,%d\r\n",char,int)#define configASSERT(x) if((x)==0) vAssertCalled(__FILE__,__LINE__)/************************************************************************ * FreeRTOS基础配置配置选项 *********************************************************************//* 置1:RTOS使用抢占式调度器;置0:RTOS使用协作式调度器(时间片) * * 注:在多任务管理机制上,操作系统可以分为抢占式和协作式两种。 * 协作式操作系统是任务主动释放CPU后,切换到下一个任务。 * 任务切换的时机完全取决于正在运行的任务。 */#define configUSE_PREEMPTION 1//1使能时间片调度(默认式使能的)#define configUSE_TIME_SLICING 1 /* 某些运行FreeRTOS的硬件有两种方法选择下一个要执行的任务: * 通用方法和特定于硬件的方法(以下简称“特殊方法”)。 * * 通用方法: * 1.configUSE_PORT_OPTIMISED_TASK_SELECTION 为 0 或者硬件不支持这种特殊方法。 * 2.可以用于所有FreeRTOS支持的硬件 * 3.完全用C实现,效率略低于特殊方法。 * 4.不强制要求限制最大可用优先级数目 * 特殊方法: * 1.必须将configUSE_PORT_OPTIMISED_TASK_SELECTION设置为1。 * 2.依赖一个或多个特定架构的汇编指令(一般是类似计算前导零[CLZ]指令)。 * 3.比通用方法更高效 * 4.一般强制限定最大可用优先级数目为32 * 一般是硬件计算前导零指令,如果所使用的,MCU没有这些硬件指令的话此宏应该设置为0! */#define configUSE_PORT_OPTIMISED_TASK_SELECTION 1 /* 置1:使能低功耗tickless模式;置0:保持系统节拍(tick)中断一直运行 * 假设开启低功耗的话可能会导致下载出现问题,因为程序在睡眠中,可用以下办法解决 * * 下载方法: * 1.将开发版正常连接好 * 2.按住复位按键,点击下载瞬间松开复位按键 * * 1.通过跳线帽将 BOOT 0 接高电平(3.3V) * 2.重新上电,下载 * * 1.使用FlyMcu擦除一下芯片,然后进行下载 * STMISP -> 清除芯片(z) */#define configUSE_TICKLESS_IDLE 0 /* * 写入实际的CPU内核时钟频率,也就是CPU指令执行频率,通常称为Fclk * Fclk为供给CPU内核的时钟信号,我们所说的cpu主频为 XX MHz, * 就是指的这个时钟信号,相应的,1/Fclk即为cpu时钟周期; */#define configCPU_CLOCK_HZ (SystemCoreClock)//RTOS系统节拍中断的频率。即一秒中断的次数,每次中断RTOS都会进行任务调度#define configTICK_RATE_HZ (( TickType_t )1000)//可使用的最大优先级#define configMAX_PRIORITIES (32)//空闲任务使用的堆栈大小#define configMINIMAL_STACK_SIZE ((unsigned short)128) //任务名字字符串长度#define configMAX_TASK_NAME_LEN (16) //系统节拍计数器变量数据类型,1表示为16位无符号整形,0表示为32位无符号整形#define configUSE_16_BIT_TICKS 0 //空闲任务放弃CPU使用权给其他同优先级的用户任务#define configIDLE_SHOULD_YIELD 1 //启用队列#define configUSE_QUEUE_SETS 1 //开启任务通知功能,默认开启#define configUSE_TASK_NOTIFICATIONS 1 //使用互斥信号量#define configUSE_MUTEXES 1 //使用递归互斥信号量 #define configUSE_RECURSIVE_MUTEXES 1 //为1时使用计数信号量#define configUSE_COUNTING_SEMAPHORES 1/* 设置可以注册的信号量和消息队列个数 */#define configQUEUE_REGISTRY_SIZE 10 #define configUSE_APPLICATION_TASK_TAG 0 /***************************************************************** FreeRTOS与内存申请有关配置选项 *****************************************************************///支持动态内存申请#define configSUPPORT_DYNAMIC_ALLOCATION 1 //支持静态内存#define configSUPPORT_STATIC_ALLOCATION 0 //系统所有总的堆大小#define configTOTAL_HEAP_SIZE ((size_t)(36*1024)) /*************************************************************** FreeRTOS与钩子函数有关的配置选项 **************************************************************//* 置1:使用空闲钩子(Idle Hook类似于回调函数);置0:忽略空闲钩子 * * 空闲任务钩子是一个函数,这个函数由用户来实现, * FreeRTOS规定了函数的名字和参数:void vApplicationIdleHook(void ), * 这个函数在每个空闲任务周期都会被调用 * 对于已经删除的RTOS任务,空闲任务可以释放分配给它们的堆栈内存。 * 因此必须保证空闲任务可以被CPU执行 * 使用空闲钩子函数设置CPU进入省电模式是很常见的 * 不可以调用会引起空闲任务阻塞的API函数 */#define configUSE_IDLE_HOOK 0 /* 置1:使用时间片钩子(Tick Hook);置0:忽略时间片钩子 * * * 时间片钩子是一个函数,这个函数由用户来实现, * FreeRTOS规定了函数的名字和参数:void vApplicationTickHook(void ) * 时间片中断可以周期性的调用 * 函数必须非常短小,不能大量使用堆栈, * 不能调用以”FromISR" 或 "FROM_ISR”结尾的API函数 */ /*xTaskIncrementTick函数是在xPortSysTickHandler中断函数中被调用的。因此,vApplicationTickHook()函数执行的时间必须很短才行*/#define configUSE_TICK_HOOK 0 //使用内存申请失败钩子函数#define configUSE_MALLOC_FAILED_HOOK 0 /* * 大于0时启用堆栈溢出检测功能,如果使用此功能 * 用户必须提供一个栈溢出钩子函数,如果使用的话 * 此值可以为1或者2,因为有两种栈溢出检测方法 */#define configCHECK_FOR_STACK_OVERFLOW 0 /******************************************************************** FreeRTOS与运行时间和任务状态收集有关的配置选项 **********************************************************************///启用运行时间统计功能#define configGENERATE_RUN_TIME_STATS 0 //启用可视化跟踪调试#define configUSE_TRACE_FACILITY 0 /* 与宏configUSE_TRACE_FACILITY同时为1时会编译下面3个函数 * prvWriteNameToBuffer() * vTaskList(), * vTaskGetRunTimeStats()*/#define configUSE_STATS_FORMATTING_FUNCTIONS 1 /******************************************************************** FreeRTOS与协程有关的配置选项 *********************************************************************///启用协程,启用协程以后必须添加文件croutine.c#define configUSE_CO_ROUTINES 0 //协程的有效优先级数目#define configMAX_CO_ROUTINE_PRIORITIES ( 2 ) /*********************************************************************** FreeRTOS与软件定时器有关的配置选项 **********************************************************************/ //启用软件定时器#define configUSE_TIMERS 1 //软件定时器优先级#define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES-1) //软件定时器队列长度#define configTIMER_QUEUE_LENGTH 10 //软件定时器任务堆栈大小#define configTIMER_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE*2) /************************************************************ FreeRTOS可选函数配置选项 ************************************************************/#define INCLUDE_xTaskGetSchedulerState 1 #define INCLUDE_vTaskPrioritySet 1#define INCLUDE_uxTaskPriorityGet 1#define INCLUDE_vTaskDelete 1#define INCLUDE_vTaskCleanUpResources 1#define INCLUDE_vTaskSuspend 1#define INCLUDE_vTaskDelayUntil 1#define INCLUDE_vTaskDelay 1#define INCLUDE_eTaskGetState 1#define INCLUDE_xTimerPendFunctionCall 1//#define INCLUDE_xTaskGetCurrentTaskHandle 1//#define INCLUDE_uxTaskGetStackHighWaterMark 0//#define INCLUDE_xTaskGetIdleTaskHandle 0/****************************************************************** FreeRTOS与中断有关的配置选项 ******************************************************************/#ifdef __NVIC_PRIO_BITS #define configPRIO_BITS __NVIC_PRIO_BITS#else #define configPRIO_BITS 4 #endif//中断最低优先级#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15 //系统可管理的最高中断优先级#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 #define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) ) /* 240 */#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )/**************************************************************** FreeRTOS与中断服务函数有关的配置选项 ****************************************************************/#define xPortPendSVHandler PendSV_Handler#define vPortSVCHandler SVC_Handler/* 以下为使用Percepio Tracealyzer需要的东西,不需要时将 configUSE_TRACE_FACILITY 定义为 0 */#if ( configUSE_TRACE_FACILITY == 1 )#include "trcRecorder.h"#define INCLUDE_xTaskGetCurrentTaskHandle 1 // 启用一个可选函数(该函数被 Trace源码使用,默认该值为0 表示不用)#endif#endif /* FREERTOS_CONFIG_H */修改stm32f10x_it.cSysTick中断服务函数是一个非常重要的函数,FreeRTOS所有跟时间相关的事情都在里面处理,SysTick就是FreeRTOS的一个心跳时钟,驱动着FreeRTOS的运行,就像人的心跳一样,假如没有心跳,我们就相当于“死了”,同样的,FreeRTOS没有了心跳,那么它就会卡死在某个地方,不能进行任务调度,不能运行任何的东西,因此我们需要实现一个FreeRTOS的心跳时钟,FreeRTOS帮我们实现了SysTick的启动的配置:在port.c文件中已经实现vPortSetupTimerInterrupt()函数,并且FreeRTOS通用的SysTick中断服务函数也实现了:在port.c文件中已经实现xPortSysTickHandler()函数,所以移植的时候只需要我们在stm32f10x_it.c文件中实现我们对应(STM32)平台上的SysTick_Handler()函数即可。FreeRTOS为开发者考虑得特别多,PendSV_Handler()与SVC_Handler()这两个很重要的函数都帮我们实现了,在在port.c文件中已经实现xPortPendSVHandler()与vPortSVCHandler()函数,防止我们自己实现不了,那么在stm32f10x_it.c中就需要我们注释掉PendSV_Handler()与SVC_Handler()这两个函数了。 ...

October 15, 2019 · 3 min · jiezi

从0开始学FreeRTOS列表列表项6

FreeRTOS列表&列表项的源码解读第一次看列表与列表项的时候,感觉很像是链表,虽然我自己的链表也不太会,但是就是感觉很像。 在FreeRTOS中,列表与列表项使用得非常多,是FreeRTOS的一个数据结构,学习过数据结构的同学都知道,数据结构能使我们处理数据更加方便快速,能快速找到数据,在FreeRTOS中,这种列表与列表项更是必不可少的,能让我们的系统跑起来更加流畅迅速。 言归正传,FreeRTOS中使用了大量的列表(List)与列表项(Listitem),在FreeRTOS调度器中,就是用到这些来跟着任务,了解任务的状态,处于挂起、阻塞态、还是就绪态亦或者是运行态。这些信息都会在各自任务的列表中得到。 看任务控制块(tskTaskControlBlock)中的两个列表项: ListItem_t xStateListItem; /* <任务的状态列表项目引用的列表表示该任务的状态(就绪,已阻止,暂停)。*/ListItem_t xEventListItem; /* <用于从事件列表中引用任务。*/一个是状态的列表项,一个是事件列表项。他们在创建任务就会被初始化,列表项的初始化是根据实际需要来初始化的,下面会说。 FreeRTOS列表&列表项的结构体既然知道列表与列表项的重要性,那么我们来解读FreeRTOS中的list.c与list.h的源码吧。从头文件lsit.h开始,看到定义了一些结构体: struct xLIST_ITEM{ listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE /* <如果configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设置为1,则设置为已知值。*/ configLIST_VOLATILE TickType_t xItemValue; /* <正在列出的值。在大多数情况下,这用于按降序对列表进行排序。 */ struct xLIST_ITEM * configLIST_VOLATILE pxNext; /* <指向列表中下一个ListItem_t的指针。 */ struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; /* <指向列表中前一个ListItem_t的指针。 */ void * pvOwner; /* <指向包含列表项目的对象(通常是TCB)的指针。因此,包含列表项目的对象与列表项目本身之间存在双向链接。 */ void * configLIST_VOLATILE pvContainer; /* <指向此列表项目所在列表的指针(如果有)。 */ listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE /* <如果configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设置为1,则设置为已知值。*/};typedef struct xLIST_ITEM ListItem_t; /* 由于某种原因,lint希望将其作为两个单独的定义。 */列表项结构体的一些注意的地方: xItemValue 用于列表项的排序,类似1—2—3—4 pxNext 指向下一个列表项的指针pxPrevious 指向上(前)一个列表项的指针 ...

October 15, 2019 · 6 min · jiezi

从0开始学FreeRTOS消息队列5

问题解答曾经有人问我,FreeRTOS那么多API,到底怎么记住呢?我想说,其实API不难记,就是有点难找,因为FreeRTOS的API很多都是带参宏,所以跳来跳去的比较麻烦,而且注释也很多,要找还真不是那么容易,不过也没啥,一般都会有API手册的,我就告诉大家一下:FreeRTOS Kernel: Reference ManualFreeRTOS内核:参考手册,大家可以在官网下载,也能在后台得到。当然书本是英文的,如果英语像我这样子不咋地的同学,可以用谷歌浏览器在官网直接看API手册,直接翻译一下就行了。传送门:https://www.freertos.org/a00018.html FreeRTOS消息队列基于 FreeRTOS 的应用程序由一组独立的任务构成——每个任务都是具有独立权限的程序。这些独立的任务之间的通讯与同步一般都是基于操作系统提供的IPC通讯机制,而FreeRTOS 中所有的通信与同步机制都是基于队列实现的。消息队列是一种常用于任务间通信的数据结构,队列可以在任务与任务间、中断和任务间传送信息,实现了任务接收来自其他任务或中断的不固定长度的消息。任务能够从队列里面读取消息,当队列中的消息是空时,挂起读取任务,用户还可以指定挂起的任务时间;当队列中有新消息时,挂起的读取任务被唤醒并处理新消息,消息队列是一种异步的通信方式。 队列特性1.数据存储队列可以保存有限个具有确定长度的数据单元。队列可以保存的最大单元数目被称为队列的“深度”。在队列创建时需要设定其深度和每个单元的大小。通常情况下,队列被作为 FIFO(先进先出)缓冲区使用,即数据由队列尾写入,从队列首读出。当然,由队列首写入也是可能的。往队列写入数据是通过字节拷贝把数据复制存储到队列中;从队列读出数据使得把队列中的数据拷贝删除。 2.读阻塞当某个任务试图读一个队列时,其可以指定一个阻塞超时时间。在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列数据有效。当其它任务或中断服务例程往其等待的队列中写入了数据,该任务将自动由阻塞态转移为就绪态。当等待的时间超过了指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转移为就绪态。由于队列可以被多个任务读取,所以对单个队列而言,也可能有多个任务处于阻塞状态以等待队列数据有效。这种情况下,一旦队列数据有效,只会有一个任务会被解除阻塞,这个任务就是所有等待任务中优先级最高的任务。而如果所有等待任务的优先级相同,那么被解除阻塞的任务将是等待最久的任务。 说些题外话,ucos中是具有广播消息的,当有多个任务阻塞在队列上,当发送消息的时候可以选择广播消息,那么这些阻塞的任务都能被解除阻塞。3.写阻塞与读阻塞想反,任务也可以在写队列时指定一个阻塞超时时间。这个时间是当被写队列已满时,任务进入阻塞态以等待队列空间有效的最长时间。由于队列可以被多个任务写入,所以对单个队列而言,也可能有多个任务处于阻塞状态以等待队列空间有效。这种情况下,一旦队列空间有效,只会有一个任务会被解除阻塞,这个任务就是所有等待任务中优先级最高的任务。而如果所有等待任务的优先级相同,那么被解除阻塞的任务将是等待最久的任务。 消息队列的工作流程1.发送消息任务或者中断服务程序都可以给消息队列发送消息,当发送消息时,如果队列未满或者允许覆盖入队, FreeRTOS 会将消息拷贝到消息队列队尾,否则,会根据用户指定的阻塞超时时间进行阻塞,在这段时间中,如果队列一直不允许入队,该任务将保持阻塞状态以等待队列允许入队。当其它任务从其等待的队列中读取入了数据(队列未满),该任务将自动由阻塞态转为就绪态。当任务等待的时间超过了指定的阻塞时间,即使队列中还不允许入队,任务也会自动从阻塞态转移为就绪态,此时发送消息的任务或者中断程序会收到一个错误码 errQUEUE_FULL。发送紧急消息的过程与发送消息几乎一样,唯一的不同是,当发送紧急消息时,发送的位置是消息队列队头而非队尾,这样,接收者就能够优先接收到紧急消息,从而及时进行消息处理。下面是消息队列的发送API接口,函数中有FromISR则表明在中断中使用的。 1 /*-----------------------------------------------------------*/ 2 BaseType_t xQueueGenericSend( QueueHandle_t xQueue, (1) 3 const void * const pvItemToQueue, (2) 4 TickType_t xTicksToWait, (3) 5 const BaseType_t xCopyPosition ) (4) 6 { 7 BaseType_t xEntryTimeSet = pdFALSE, xYieldRequired; 8 TimeOut_t xTimeOut; 9 Queue_t * const pxQueue = ( Queue_t * ) xQueue;10 11 /* 已删除一些断言操作 */12 13 for ( ;; ) {14 taskENTER_CRITICAL(); (5)15 {16 /* 队列未满 */17 if ( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength )18 || ( xCopyPosition == queueOVERWRITE ) ) { (6) 19 traceQUEUE_SEND( pxQueue );20 xYieldRequired =21 prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition ); (7)22 23 /* 已删除使用队列集部分代码 */24 /* 如果有任务在等待获取此消息队列 */25 if ( listLIST_IS_EMPTY(&(pxQueue->xTasksWaitingToReceive))==pdFALSE){ (8)26 /* 将任务从阻塞中恢复 */27 if ( xTaskRemoveFromEventList(28 &( pxQueue->xTasksWaitingToReceive ) )!=pdFALSE) { (9)29 /* 如果恢复的任务优先级比当前运行任务优先级还高,30 那么需要进行一次任务切换 */31 queueYIELD_IF_USING_PREEMPTION(); (10)32 } else {33 mtCOVERAGE_TEST_MARKER();34 }35 } else if ( xYieldRequired != pdFALSE ) {36 /* 如果没有等待的任务,拷贝成功也需要任务切换 */37 queueYIELD_IF_USING_PREEMPTION(); (11)38 } else {39 mtCOVERAGE_TEST_MARKER();40 }41 42 taskEXIT_CRITICAL(); (12)43 return pdPASS;44 }45 /* 队列已满 */46 else { (13)47 if ( xTicksToWait == ( TickType_t ) 0 ) {48 /* 如果用户不指定阻塞超时时间,退出 */49 taskEXIT_CRITICAL(); (14)50 traceQUEUE_SEND_FAILED( pxQueue );51 return errQUEUE_FULL;52 } else if ( xEntryTimeSet == pdFALSE ) { 53 /* 初始化阻塞超时结构体变量,初始化进入54 阻塞的时间xTickCount和溢出次数xNumOfOverflows */55 vTaskSetTimeOutState( &xTimeOut ); (15)56 xEntryTimeSet = pdTRUE;57 } else {58 mtCOVERAGE_TEST_MARKER();59 }60 }61 }62 taskEXIT_CRITICAL(); (16)63 /* 挂起调度器 */64 vTaskSuspendAll();65 /* 队列上锁 */66 prvLockQueue( pxQueue );67 68 /* 检查超时时间是否已经过去了 */69 if (xTaskCheckForTimeOut(&xTimeOut, &xTicksToWait)==pdFALSE){ (17)70 /* 如果队列还是满的 */71 if ( prvIsQueueFull( pxQueue ) != pdFALSE ) { (18) 72 traceBLOCKING_ON_QUEUE_SEND( pxQueue ); 73 /* 将当前任务添加到队列的等待发送列表中74 以及阻塞延时列表,延时时间为用户指定的超时时间xTicksToWait */75 vTaskPlaceOnEventList(76 &( pxQueue->xTasksWaitingToSend ), xTicksToWait );(19)77 /* 队列解锁 */78 prvUnlockQueue( pxQueue ); (20)79 80 /* 恢复调度器 */81 if ( xTaskResumeAll() == pdFALSE ) {82 portYIELD_WITHIN_API();83 }84 } else {85 /* 队列有空闲消息空间,允许入队 */86 prvUnlockQueue( pxQueue ); (21)87 ( void ) xTaskResumeAll();88 }89 } else {90 /* 超时时间已过,退出 */91 prvUnlockQueue( pxQueue ); (22)92 ( void ) xTaskResumeAll();93 94 traceQUEUE_SEND_FAILED( pxQueue );95 return errQUEUE_FULL;96 }97 }98 }99 /*-----------------------------------------------------------*/如果阻塞时间不为 0,任务会因为等待入队而进入阻塞, 在将任务设置为阻塞的过程中, 系统不希望有其它任务和中断操作这个队列的 xTasksWaitingToReceive 列表和 xTasksWaitingToSend 列表,因为可能引起其它任务解除阻塞,这可能会发生优先级翻转。比如任务 A 的优先级低于当前任务,但是在当前任务进入阻塞的过程中,任务 A 却因为其它原因解除阻塞了,这显然是要绝对禁止的。因此FreeRTOS 使用挂起调度器禁止其它任务操作队列,因为挂起调度器意味着任务不能切换并且不准调用可能引起任务切换的 API 函数。但挂起调度器并不会禁止中断,中断服务函数仍然可以操作队列阻塞列表,可能会解除任务阻塞、可能会进行上下文切换,这也是不允许的。于是,FreeRTOS解决办法是不但挂起调度器,还要给队列上锁,禁止任何中断来操作队列。下面来看看流程图:相比在任务中调用的发送函数,在中断中调用的函数会更加简单一些, 没有任务阻塞操作。函数 xQueueGenericSend中插入数据后, 会检查等待接收链表是否有任务等待,如果有会恢复就绪。如果恢复的任务优先级比当前任务高, 则会触发任务切换;但是在中断中调用的这个函数的做法是返回一个参数标志是否需要触发任务切换,并不在中断中切换任务。在任务中调用的函数中有锁定和解锁队列的操作, 锁定队列的时候, 队列的事件链表不能被修改。 而在被中断中发送消息的处理是: 当遇到队列被锁定的时候, 将新数据插入到队列后, 并不会直接恢复因为等待接收的任务, 而是累加了计数, 当队列解锁的时候, 会根据这个计数, 对应恢复几个任务。遇到队列满的情况, 函数会直接返回, 而不是阻塞等待, 因为在中断中阻塞是不允许的!!! ...

October 15, 2019 · 5 min · jiezi

SharpC-A-C-Interpreter-In-C-1000

函数的实现如下: public class FunctionDefine : Context{ private Stack<List<Expression.Operand.Value>> m_parameterStack; public DataTypeInfo ReturnType; public Expression.Operand.Operand ReturnValue; public bool IsVariableArgument; public int ReferenceCount; public int IteratorCount = 0; public List<Context> ArgumentDefinitions; public Block Body; ...其查找方法FindByName需要搜索参数列表: public override Context FindByName(string str){ if (ArgumentDefinitions != null) { foreach (FunctionArgumentDefine arg in ArgumentDefinitions) { if (arg.Name == str) return arg; } } return base.FindByName(str);}其运行方法实现如下: public virtual void Run(Context ctx, List<Expression.Operand.Operand> parameters){ Debug.WriteLine(string.Format("Call function \"{0}\" with [{1}] parameter{2}", Name, parameters.Count, parameters.Count > 0 ? "s" : "")); // 准备工作 BeforeRun(ctx, parameters); Run(ctx); // 清场工作 AfterRun(ctx); }准备工作包括: ...

October 15, 2019 · 5 min · jiezi

SharpC-A-C-Interpreter-In-C-0110

if勿庸质疑,说到控制结构,首先会想到if。其实现应如下所示: public class IfThen : ControlFlow{ public Expression.ExpressionNode Condition; public Block ThenClause { get { return Children.Count > 0 ? Children.First() as Block: null; } } public Block ElseClause { get { return Children.Count > 1 ? Children.Last() as Block: null; } }运行时处理则应该如下: public override void Run(Context ctx){ Debug.WriteLine("if(" + Condition.ToString() + ")"); Expression.Operand.Operand condVal = Condition.Evaluate(this); Debug.WriteLine("Condition=" + condVal.GetValue(this).ToString()); if (condVal.GetValue(this).AsInt != 0) { if (ThenClause != null) { Debug.WriteLine("Then"); ThenClause.Run(this); } } else { if (ElseClause != null) { Debug.WriteLine("Else"); ElseClause.Run(this); } }}看起来相当简单。 ...

October 15, 2019 · 4 min · jiezi

SharpC-A-C-Interpreter-In-C-0111

表达式,最简单也最困难。各种算术、逻辑、赋值埋同函数调用,想想都头大如斗转星移山填海枯石烂。废话有云,根据Yacc规则,表达式由操作数及操作符构成。操作数有立即数、变量、函数调用及另一个表达式。操作符有一元、二元及三元操作符。惜乎SharpC不支持三元表达式。有两类特殊的操作数:指针及指针指示(Pointer indiction应该怎么翻译?)。有一个特殊的操作符:sizeof。说其特殊是因为偶将其归类于一元操作符,且不像其它操作符般需要操作数,sizeof也可以类型名称作为操作数。先看看操作符优先级定义: public enum OperatorPriority{ // 赋值操作最低 Assign, Logic, Bitwise, // 算术运算有两级优先级:+,—是一级,*,/,%是高一级 ArthmeticLow, ArthmeticHigh, // 拔高了移位操作符优先级于算术之上 Shift, Address, Unary, Parenthese}先看看表达式求值过程: public Operand.Operand Evaluate(Context ctx){ if (Token is Operand.Operand) { if (Token is Operand.ValueOfFunctionCalling) { Operand.ValueOfFunctionCalling valOfFuncCalling = Token as Operand.ValueOfFunctionCalling; return valOfFuncCalling.GetValue(ctx); } else return Token as Operand.Operand; } else { List<Operand.Operand> operands = new List<Operand.Operand>(); if (LeftNode != null) operands.Add(LeftNode.Evaluate(ctx)); if (RightNode != null) operands.Add(RightNode.Evaluate(ctx)); Operand.Operand res = (Token as Operator.Operator).Evaluate(ctx, operands); Debug.WriteLine(string.Format("\tExp: [{0} {1} {2}] Result: [{3}]", operands.First().ToString(), Token.ToString(), operands.Count > 1 ? operands.Last().ToString() : "", res.ToString())); return res; }}表达式求值过程: ...

October 15, 2019 · 5 min · jiezi

SharpC-A-C-Interpreter-In-C-1100

偶有闲暇,添加了对结构的支持。也增加了对typedef structname synonym的支持。技巧: 结构成员在解析时转换成相对结构于的起始地址之偏移。在运行时计算成员的实际地址(运行时变量才会分配地址 )。成员按ValueOfPointerIndiction方式访问。示例代码: struct StructA { int a; int b; }; StructA theA; theA.a = 1; theA.b = 2; print("theA.a=%i\ntheA.b=%i", theA.a, theA.b); StructA *ptheA = &theA; ptheA->a = 3; ptheA->b = 4; print("theA.a=%i\ntheA.b=%i", theA.a, theA.b); print("ptheA->a=%i\nptheA->b=%i", ptheA->a, ptheA->b);结果输出:解析: SharpC.Grammar.Variable: Line: 192 Pos: 1 "theA;"SharpC.Grammar.Statement: Line: 194 Pos: 1 "theA.a = 1;"SharpC.Grammar.Statement: Line: 195 Pos: 1 "theA.b = 2;"SharpC.Grammar.Statement: Line: 197 Pos: 1 "print("theA.a=%i\ntheA.b=%i", theA.a, theA.b);"SharpC.Grammar.Variable: Line: 199 Pos: 1 "ptheA = &theA;"SharpC.Grammar.Statement: Line: 199 Pos: 1 "ptheA = &theA;"SharpC.Grammar.Statement: Line: 201 Pos: 1 "ptheA->a = 3;"SharpC.Grammar.Statement: Line: 202 Pos: 1 "ptheA->b = 4;"SharpC.Grammar.Statement: Line: 204 Pos: 1 "print("theA.a=%i\ntheA.b=%i", theA.a, theA.b);"SharpC.Grammar.Statement: Line: 205 Pos: 1 "print("ptheA->a=%i\nptheA->b=%i", ptheA->a, ptheA->b);"运行: ...

October 15, 2019 · 3 min · jiezi

SharpC-A-C-Interpreter-In-C-1011

现在,来看看成果。 以如下C代码为运行样本: int global_a = 1 + 2;int global_b = 1 - 2;int global_c = 1 * 2;int global_d = 1 / 2;int global_e = 1 % 2;int global_f = 1 & 2;int global_g = 1 | 2;int global_h = 1 ^ 2;int global_i = 1 && 2;int global_j = 1 == 2;int global_k = 1 > 2;int global_l = 1 >= 2;int global_m = 1 < 2;int global_n = 1 <= 2;int global_o = 1 != 2;int global_p = 1 || 2;int global_q = 1 << 2;int global_r = 1 >> 2;char global_ch = 'a';char* global_str = "abcdefghijklmn";void fork(){ int x = 1; if (x > 0) x = 2; else x = 3; // return ; x = 2; for(int i = 0; i < 5; i++) { x++; break; } while(x < 10) x++; x = 0; do { x++; }while(x < 5); x = 2; switch(x) { case 1: case 2: x++; case 3: break; case 4: default: break; } // switch in for for(x = 1; x < 10; x++) { switch(x) { case 1: break; case 2: case 3: { if (x == 2) x = 2; else x = 3; } break; case 4: { for(int y = 0; y < 5; y++) { if (y >= 3) break; } } break; case 6: break; case 7: case 8: default: { x = x; } break; } // switch } // for}void fork2(int a, int b){ print("iterate fork2: a=%i, b=%i\n", a, b); if (a + b == 2) return; else fork2(--a, --b);}void fork3(...){ int len = varlen(); print("Variable function:fork3\n"); print("length is %i\n", len); for(int i = 0; i < len; i++) { int x = vararg(i); print("x=%i\n", x); } return ;}int strlen(char* str){ int len = 0; char* ptr = str; while(*ptr != 0) { len++; ptr++; } return len;}void main(){ fork(); fork2(5, 5); fork3(1); fork3(1, 2); fork3(1, 2, 3); // Test pointer assign char* str = "abcdefg"; char* str2 = str; print("str=%s\n", str); print("str2=%s\n", str2); print("str len=%i\n", strlen(str)); print("str2 len=%i\n", strlen(str2)); char* str3 = malloc(5); *(str3 + 0) = 'a'; *(str3 + 1) = 'b'; *(str3 + 2) = 0; print("str3=%s\n", str3); int iVal = 0; if (input("Test Input", "Please input a string", "0", "%i", "Integer required", &iVal)) { print("result=%i \n", iVal); } switch(confirm("hello", "world")) { case 0: prompt("Result", "You pressed no."); break; case 1: prompt("Result", "You pressed yes."); break; }}运行之,则有:提示输入: ...

October 15, 2019 · 25 min · jiezi

SharpC-A-C-Interpreter-In-C-1010

表达式解析中比较重要的是表达式树中操作符的插入,需要比较优先级: private bool AddOperatorNode(ref Expression.ExpressionNode expTree, Expression.Operator.Operator op){ Expression.ExpressionNode node = new Expression.ExpressionNode() { Token = op }; if (expTree != null && expTree.Token is Expression.Operand.Operand) { node.LeftNode = expTree; expTree = node; } else { if (expTree == null) { expTree = node; } else { if (node.Token <= expTree.Token) { node.LeftNode = expTree; expTree = node; } else { Expression.ExpressionNode parent = null; Expression.ExpressionNode root = expTree; do { // Operand ? if (root.Token is Expression.Operand.Operand) { if (parent != null) { parent.RightNode = node; node.LeftNode = root; break; } else { node.LeftNode = root; root = node; break; } } // Less priority, new node will be add to right child node if (root.Token <= node.Token) { if (root.RightNode == null) { root.RightNode = node; break; } parent = root; root = root.RightNode; } else { // Higher priority if (parent == null) { node.LeftNode = root; expTree = node; } else { parent.RightNode = node; node.LeftNode = root; } break; } } while (true); } } } m_lastExpNode = node; return true;} // func接着是添加操作数的操作,需要对类型匹配进行验证: ...

October 15, 2019 · 7 min · jiezi

SharpC-A-C-Interpreter-In-C-1001

在了解词法分析之前,先看看对单词的定义: /// <summary>/// A structure for the result of word parsing./// </summary>public class Word{ public int AbsoluteLine; public int AbsoluteStartPos; public int AbsoluteEndPos; public int Offset; public int Column; public string Text; public bool IsAvailable; public Context.LocationInfo Location; }注意为了方便计,实现中并未将所有词语都标记为单词,比如操作符。仅有关键字、变量名和函数名被标记为单词。为了更好的分析源代码,创建了一个SourceCode类,以进行词法分析工作: public class SourceCode{ //各种标记 public static char[] MultiLineCommentsStartMark = new char[] { '/', '*' }; public static char[] HexMark = new char[] { 'x', 'X' }; public static char[] ExponentialMark = new char[] { 'e', 'E' }; public static char[] FloatMark = new char[] { 'f', 'F' }; public static char[] PlusMinusMark = new char[] { '-', '+' }; public string Text = ""; public int Line = 0; public int ColumnOfCurrentLine = 0; public int PosOffset = 0; public int LineOffset = 0; public int Column; public Word LastWord; public SourceCode() { } public SourceCode(string txt); public void LoadFromFile(string path); public bool Eof; // 当前位置在整个代码中的绝对位置 public int AbsolutePos; // 当前行在整个代码中的绝对行 public int AbsoluteLine; public Context.LocationInfo Location; public char CurrentChar; // 重置位置索引(局部位置索引) public void ResetPos(); // 检测下一个字符是否与指定的字符匹配 public bool TestNextChar(char c); // 检测下一个字符是否在指定的字符集中 public bool TestNextChar(char[] chars); // 获取下一个字符,默认会跳过前面的空格 public void NextChar(bool skipSpace = true); // 获取从当前位置开始的剩余代码 public string Tail; public static bool IsDigit(char ch); public static bool IsLetter(char ch); public static bool IsSpace(char ch); public static bool IsOperator(char ch); public static bool IsBracket(char ch); public void SkipSpace(); // 以';'为分隔符,划分代码片断 public List<SourceCode> SplitStatement(); // 以','为分隔符,划分多个变量定义片断 public List<SourceCode> SplitMultiDeclaration(); // 划分参数 public List<SourceCode> SplitParameter(); // 获取一个单词 public static Word GetWord(SourceCode src); // 获取括号内的代码 public static SourceCode GetBracketCode(char leftBracket, char rightBracket, SourceCode src);}更复杂的分析,与语法分析一道,整合到Parser类中。Parser类用于对C代码进行语法分析并构造语法树。 ...

October 15, 2019 · 10 min · jiezi

从0开始学FreeRTOS列表与列表项3

FreeRTOS列表&列表项的源码解读    第一次看列表与列表项的时候,感觉很像是链表,虽然我自己的链表也不太会,但是就是感觉很像。 在FreeRTOS中,列表与列表项使用得非常多,是FreeRTOS的一个数据结构,学习过数据结构的同学都知道,数据结构能使我们处理数据更加方便快速,能快速找到数据,在FreeRTOS中,这种列表与列表项更是必不可少的,能让我们的系统跑起来更加流畅迅速。 言归正传,FreeRTOS中使用了大量的列表(List)与列表项(Listitem),在FreeRTOS调度器中,就是用到这些来跟着任务,了解任务的状态,处于挂起、阻塞态、还是就绪态亦或者是运行态。这些信息都会在各自任务的列表中得到。 看任务控制块(tskTaskControlBlock)中的两个列表项: ListItem_t xStateListItem; / * <任务的状态列表项目引用的列表表示该任务的状态(就绪,已阻止,暂停)。*/ListItem_t xEventListItem; / * <用于从事件列表中引用任务。*/一个是状态的列表项,一个是事件列表项。他们在创建任务就会被初始化,列表项的初始化是根据实际需要来初始化的,下面会说。 FreeRTOS列表&列表项的结构体既然知道列表与列表项的重要性,那么我们来解读FreeRTOS中的list.c与list.h的源码吧。从头文件lsit.h开始,看到定义了一些结构体: struct xLIST_ITEM{listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE / * <如果configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设置为1,则设置为已知值。* /configLIST_VOLATILE TickType_t xItemValue; / * <正在列出的值。在大多数情况下,这用于按降序对列表进行排序。 * /struct xLIST_ITEM * configLIST_VOLATILE pxNext; / * <指向列表中下一个ListItem_t的指针。 * /struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; / * <指向列表中前一个ListItem_t的指针。 * /void * pvOwner; / * <指向包含列表项目的对象(通常是TCB)的指针。因此,包含列表项目的对象与列表项目本身之间存在双向链接。 * /void * configLIST_VOLATILE pvContainer; / * <指向此列表项目所在列表的指针(如果有)。 * /listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE / * <如果configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设置为1,则设置为已知值。* /};typedef struct xLIST_ITEM ListItem_t; / *由于某种原因,lint希望将其作为两个单独的定义。 * /列表项结构体的一些注意的地方: xItemValue 用于列表项的排序,类似1—2—3—4 ...

October 15, 2019 · 6 min · jiezi

第10课-struct和union分析

·C语言中的struct可以看做变量的集合不同类型的变量放在一起同时使用 例子10-1: include "stdio.h"struct Ts {};int main(){ struct Ts t1;struct Ts t2;printf("sizeof(struct Ts) = %d\n", sizeof(struct Ts));printf("sizeof(t1) = %d\n", sizeof(t1));printf("sizeof(t1) = %d\n", sizeof(t2));return 0;}输出结果:error;代码中不允许出现空结构体 空结构体占用的内存为0; 结构体与柔性数组·柔性数组即数组大小待定的数组·C语言中可以由结构体产生柔性数组·C语言中结构体的最后一个元素可以是大小未知的数组 例子10-1-1: include "stdio.h"struct soft_array{ int len;int array[];};int main(){ printf("sizeof(struct soft_array) = %d\n", sizeof(struct soft_array));return 0;} 输出结果: sizeof(struct soft_array) = 4 int array[]在代码中不占用内存,只是一个标识。 柔性数组使用分析例子10-2: include "stdio.h"include "malloc.h"struct SoftArray{ int len;int array[];};struct SoftArray* create_soft_array(int size){ struct SoftArray *ret = NULL;if (size > 0){ ret = (struct SoftArray*)malloc(sizeof(struct SoftArray) + sizeof(int)*size); ret->len = size;}return ret;};void delete_soft_array(struct SoftArray* sa) { ...

October 14, 2019 · 1 min · jiezi

Swoole协程之旅

作者:韩天峰 原文地址:点击查看 协程是什么?概念其实很早就出现了,摘wiki一段:According to Donald Knuth, the term coroutine was coined by Melvin Conway in 1958, after he applied it to construction of an assembly program.The first published explanation of the coroutine appeared later, in 1963. 协程要比c语言的历史还要悠久,究其概念,协程是子程序的一种, 可以通过yield的方式转移程序控制权,协程之间不是调用者与被调用者的关系,而是彼此对称、平等的。协程完全有用户态程序控制,所以也被成为用户态的线程。协程由用户以非抢占的方式调度,而不是操作系统。正因为如此,没有系统调度上下文切换的开销,协程有了轻量,高效,快速等特点。(大部分为非抢占式,但是,比如Golang在1.4也加入了抢占式调度,其中一个协程发生死循环,不至于其他协程被饿死。需要在必要的时刻让出CPU) 协程近几年如此火爆,很大一部分原因归功与Golang在中国的流行和快速发展,受到很多开发的喜爱。目前支持协程的语言有很多,例如: Golang、Lua、Python、C#、JavaScript等。大家也可以用很短的代码用C/C++撸出协程的模型。当然PHP也有自己的协程实现,也就是生成器,我们这里不展开讨论。 Swoole 1.xSwoole最初以高性能网络通讯引擎的姿态进入大家视线,Swoole1.x的编码主要是异步回调的方式,虽然性能非常高效,但很多开发都会发现,随着项目工程的复杂程度增加,以异步回调的方式写业务代码是和人类正常思维相悖的,尤其是回调嵌套多层的时候,不仅开发维护成本指数级上升,而且出错的几率也大幅增加。大家理想的编码方式是:同步编码得到异步非阻塞的效果。所以Swoole很早的时候就开始了协程的探索。 最初的协程版本是基于PHP生成器GeneratorsYield的方式实现的,可以参考PHP大神Nikita的早期博客的关于协程介绍。PHP和Swoole的事件驱动的结合可以参考腾讯出团队开源的TSF框架,我们也在很多生产项目中使用了该框架,确实让大家感受到了,以同步编程的方式写异步代码的快感,然而,现实总是很残酷,这种方式有几个致命的缺点: 所有主动让出的逻辑都需要yield关键字。这会给程序员带来极大的概率犯错,导致大家对协程的理解转移到了对generators语法的原理的理解。由于语法无法兼容老的项目,改造老的项目工程复杂度巨大,成本太高。这样使得无论新老项目,使用都无法得心应手。 Swoole 2.x 2.x之后的协程都是基于内核原生的协程,无需yield关键字。2.0的版本是一个非常重要的里程碑,实现了php的栈管理,深入zend内核在协程创建,切换以及结束的时候操作PHP栈。 2.x主要使用了setjmp/longjmp的方式实现协程,很多C项目主要采用这种方式实现try-catch-finally,大家也可以参考Zend内核的用法。setjmp的首次调用返回值是0,longjmp跳转时,setjmp的返回值是传给longjmp的value。 setjmp/longjmp由于只有控制流跳转的能力。虽然可以还原PC和栈指针,但是无法还原栈帧,因此会出现很多问题。比如longjmp的时候,setjmp的作用域已经退出,当时的栈帧已经销毁。这时就会出现未定义行为。假设有这样一个调用链: func0() -> func1() -> ... -> funcN()只有在func{i}()中setjmp,在func{i+k}()中longjmp的情况下,程序的行为才是可预期的。 Swoole 3.x3.x 是生命周期很短的一个版本,主要借鉴了fiber-ext项目,使用了PHP7的VM interrupts机制,该机制可以在vm中设置标记位,在执行一些指令的时候(例如:跳转和函数调用等)检查标记位,如果命中就可以执行相应的hook函数来切换vm的栈,进而实现协程。 Swoole 4.x从 4.x 开始,Swoole 实现了双栈模式的协程内核。并且将所有IO和系统操作封装了到了底层,实现了彻底的内核协程化。另外,还提供了全新的Runtime Hook模块,可以使得已有的旧的PHP同步代码变为协程模式。 ...

October 14, 2019 · 6 min · jiezi

C语言这么厉害它自身又是用什么语言写的

这是来自我的星球的一个提问:“C语言本身用什么语言写的?” 换个角度来问,其实是:C语言在运行之前,得编译才行,那C语言的编译器从哪里来? 用什么语言来写的?如果是用C语言本身来写的,到底是先有蛋还是先有鸡? 1 我们假设世界上不存在任何编译器, 先从机器语言说起,看看怎么办。  机器语言可以直接被CPU执行,不需要编译器。 然后是汇编语言, 汇编语言虽然只是机器语言的助记符,但是也需要编译成机器语言才能执行,没办法只能用机器语言来写这第一个编译器了(以后就不用了)。  汇编语言的问题解决了,就往前迈进了一大步,这时候就可以用汇编语言去写C语言的编译器,我们说这是C编译器的老祖宗。  有了这个老祖宗,就可以编译任意的C语言程序了,那是不是可以用C语言本身写一个编译器?只要用老祖宗编译一下就可以了。 OK, 这么一层层上来,终于得到了一个用C语言写的编译器, 真是够麻烦的。  到这个时候,之前那个汇编写的C语言编译器就可以抛弃了。  当然,如果在C语言之前,已经出现了别的高级语言,例如Pascal,那就可以用Pascal来写一个C语言的编译器。 第一个Pascal的编译器据说使用Fortran写的。而做为第一个高级语言的Fortran,它的编译器应该是汇编语言写的。 2 关于编译器,这里边有个有趣的传说: 传说Unix 发明人之一的 Ken Thompson在贝尔实验室,大摇大摆的走到任何一台Unix机器前,输入自己的用户名和密码,就能以root的方式登录!  贝尔实验室人才济济,另外一些大牛发誓要把这个漏洞找出来,他们通读了Unix的C源码,终于找到了登录的后门, 清理后门以后编译Unix , 运行, 可是Thompson 还是能够登录进去。 有人觉得可能是编译器中有问题,在编译Unix的时候植入了后门, 于是他们又用C语言重新写了一个编译器,用新的编译器再次编译了Unix, 这下总算天下太平了吧。 可是仍然不管用, Thompson 依然可以用root登录,真是让人崩溃 ! 后来Thompson 本人解开了秘密,是第一个C 语言编译器有问题, 这个编译器在编译Unix源码的时候,当然会植入后门, 这还不够,更牛的是,如果你用C 语言写了一个新编译器,肯定也需要编译成二进制代码啊,用什么来编译,只有用Thompson写的那第一个编译器来编译,好了, 你写的这个编译器就会被污染了,你的编译器再去编译Unix , 也会植入后门 :-) 说到这里我就想起了几年前的XcodeGhost 事件,简单来说就是在Xcode(非官方渠道下载的)中植入了木马,这样XCode编译出的ios app都被污染了,这些app就可以被黑客利用做非法之事。  虽然这个XCodeGhost和Thompson的后面相比差得远,但是提醒我们,下载软件的时候要走正规渠道,从官方网站下载,认准网站的HTTPS标准,甚至可以验证一下checksum。 3 可能有人问:我用汇编写一段Hello World都很麻烦,居然有人可以用它写复杂的编译器?这可能吗? 当然可能,在开发第一代Unix的时候,连C语言都没有, Ken Thompson 和 Dennis Ritchie 可是用汇编一行行把Unix敲出来的。   WPS第一版是求伯君用汇编写出来的, Turbo Pascal 的编译器也是Anders 用汇编写出来的,大神们的能力不是普通人能想象得到的。  对于编译器来说,还可以采用“滚雪球”的方式来开发: 还是以C语言为例,第一个版本可以先选择C语言的一个子集,例如只支持基本的数据类型,流程控制语句,函数调用...... 我们把这个子集称为C0。 ...

October 13, 2019 · 1 min · jiezi

彻底弄懂为什么不能把栈上分配的数组字符串作为返回值

背景最近准备从 C 语言零基础到 PHP 扩展开发实战,案例的过程中准备了如下代码碎片,演示解析http scheme #include <stdio.h>#include <stdlib.h>#include <string.h>char *parse_scheme(const char *url){ char *p = strstr(url,"://"); return strndup(url,p-url);}int main(){ const char *url = "http://static.mengkang.net/upload/image/2019/0907/1567834464450406.png"; char *scheme = parse_scheme(url); printf("%s\n",scheme); free(scheme); return 0;}上面是通过strndup的方式,背后也依托了malloc,所以最后也需要free。有人在微信群私信parse_scheme能用char []来做返回值吗?我们知道栈上的数组也能用来存储字符串,那我们可以改写成下面这样吗? char *parse_scheme(const char *url){ char *p = strstr(url,"://"); long l = p - url + 1; char scheme[l]; strncpy(scheme, url, l-1); return scheme;}大多数人都知道不能这样写,因为返回的是栈上的地址,当从该函数返回之后,那段栈空间的操作权也释放了,当再次使用该地址的时候,值就是不确定的了。 那我们今天就一起探讨下出现这样情况的背后的真正原理。 基础预备每个函数运行的时候因为需要内存来存放函数参数以及局部变量等,需要给每个函数分配一段连续的内存,这段内存就叫做函数的栈帧(Stack Frame)。因为是一块连续的内存地址,所以叫帧;为什么叫要加一个栈呢?想必大家都熟悉了函数调用栈,为什么叫函数调用栈呢?比如下面的表达式 array_values(explode(",",file_get_contents(...)));函数的执行顺序是最内层的函数最先执行,然后依次返回执行外层的函数。所以函数的执行就是利用了栈的数据结构,所以就叫栈帧。 x86_64 cpu上的 rbp 寄存器存函数栈底地址,rsp 寄存器存函数栈顶地址。 ...

October 13, 2019 · 2 min · jiezi

墨尔本大学COMP90007-assignment2-课业解析

墨尔本大学COMP90007 assignment2 课业解析题意: 完成5道计算机网络问题 解析 q1:求B到F的最短路径,利用Dijkstra算法,先求B到邻节点的最短路径,为3(BC),再将BC看成一个节点求他们到其他邻节点的最短路径,为5(BCE),重复步骤,求得B到F的最短路径为7,注意更新节点的权值变化 q2:对于A,2^n-2>=2000,n>=11,所以MASK最小为11,子网掩码取21位,159.27.0.0/21 q3:数据段主要是用来建立主机端到端连接,包括TCP和UDP连接,数据被分割,这样可对每个数据段进行排序,以便在接收端按发送顺序重组数据流 涉及知识点: 最短路径,计算机网络计算COMP90007 Internet TechnologiesAssignment 2Semester 2, 2019Due date: Oct 18th Friday 11:30amThis assignment is worth 5% of the total marks for the subject. This assignment has 5 questions.The weighting of each question is shown beside the question. Answers must be submitted as aPDF file via the COMP90007 Assignment 2 submission form in the LMS which will open close tothe submission time. Late submissions will attract a penalty of 10% per day (or part thereof).Please ensure your name, user name and id are clearly presented. Submission should onlycontain the question number and the answer (do not repeat the text of questions in yoursubmission). Questions can be answered by studying the material covered. All work presentedshould be your original individual effort/work.Question 1 (1 mark)Consider a subnet in the following figure, with the weight of each edge as shown in the label.The shortest path routing is used.1) Compute the sink tree for node B using Dijkstra’s algorithm. Show your calculation and thesteps of adding all nodes to the sink tree. (It is recommended to use a table similar to theone as shown on the Slide 9 of Week 6’s lecture)2) What is the shortest path from B to F and its distance?Question 2 (1 mark)Suppose that four organisations A, B, C and D request 2000, 1700, 900, and 4000 addressesrespectively, and in that order. There are a large number of consecutive IP addresses availablestarting at 159.27.0.0, which can be assigned to these four organisations. For each of theseorganisations, give the first IP address assigned, the last IP address assigned, the number ofaddresses allocated and the mask in the w. x. y. z /s notation.Question 3 (1 mark)Different applications care about different aspects of a network transmission. For the followingapplications please annotate each bucket/aspect with three values, Low, Medium, High, whereHigh means a high need for quality for that aspect.Question 4 (1 mark)At Transport layer we use segments to send data across. Argue for using larger segments bydiscussing briefly why larger segments could be beneficial. Then also argue why using smallsegments may be beneficial.Question 5 (1 mark)In the following security protocol that we saw in class, discuss briefly what could go wrong withthe protocol if RA was not sent across from Alice to Bob. What fails in the protocol?等多可甲违心:g19963812037 ...

October 9, 2019 · 2 min · jiezi

NAT唯一五元组选取

使用iptable进行nat设置时,可以使用如下扩展选项: # SNAT 源地址转换,用在 POSTROUTING、INPUT 链--to-source [<ipaddr>[-<ipaddr>]][:port[-port]]--random # 映射到随机端口号,--random-fully # 映射到随机端口号(PRNG 完全随机化)--persistent # 映射到固定地址# DNAT 目的地址转换,用在 PREROUTING、OUTPUT 链--to-destination [<ipaddr>[-<ipaddr>]][:port[-port]]--random # 映射到随机端口号--persistent # 映射到固定地址在内核中有如下几个标志与上面的选项对应: /* 指定了IP范围 */#define NF_NAT_RANGE_MAP_IPS (1 << 0)/* 指定了端口具体范围 */#define NF_NAT_RANGE_PROTO_SPECIFIED (1 << 1)/* 范围随机,使用secure_port函数进行源端口计算,对应于--random */#define NF_NAT_RANGE_PROTO_RANDOM (1 << 2)/* 映射到固定地址,同一个客户端使用相同的源地址,对应于--persistent */#define NF_NAT_RANGE_PERSISTENT (1 << 3)/* 完全随机,对应于--random-fully */#define NF_NAT_RANGE_PROTO_RANDOM_FULLY (1 << 4)//上面几个标志有些可以组合使用//随机标志#define NF_NAT_RANGE_PROTO_RANDOM_ALL \ (NF_NAT_RANGE_PROTO_RANDOM | NF_NAT_RANGE_PROTO_RANDOM_FULLY)//范围标志#define NF_NAT_RANGE_MASK \ (NF_NAT_RANGE_MAP_IPS | NF_NAT_RANGE_PROTO_SPECIFIED | \ NF_NAT_RANGE_PROTO_RANDOM | NF_NAT_RANGE_PERSISTENT | \ NF_NAT_RANGE_PROTO_RANDOM_FULLY)构建nat信息 netfilter在两个地方会构建nat信息。一个是在命中nat规则后构建nat信息,另外一个是relate连接会构建nat信息,在expect函数中。构建nat信息都是使用函数nf_nat_setup_info进行构建,两者的差异在于range参数。后者由iptable规则设置,前者由help函数确定。nat会修改连接跟踪,仅仅修改应答方向。 ...

October 8, 2019 · 7 min · jiezi

Redis5源码学习浅析redis命令之restore篇

Grape 命令语法命令含义:反序列化给定的序列化值,并将它和给定的 key 关联。 命令格式:RESTORE key ttl serialized-value [REPLACE] [ABSTTL] [IDLETIME seconds] [FREQ frequency]命令实战redis> DEL mykey0redis> RESTORE mykey 0 "\n\x17\x17\x00\x00\x00\x12\x00\x00\x00\x03\x00\ x00\xc0\x01\x00\x04\xc0\x02\x00\x04\xc0\x03\x00\ xff\x04\x00u#<\xc0;.\xe9\xdd"OKredis> TYPE mykeylistredis> LRANGE mykey 0 -11) "1"2) "2"3) "3"返回值如果反序列化成功那么返回 OK ,否则返回一个错误。 源码分析源码分析部分我们分为几个部分来讲解。 参数处理void restoreCommand(client *c) { long long ttl, lfu_freq = -1, lru_idle = -1, lru_clock = -1; rio payload; int j, type, replace = 0, absttl = 0; robj *obj; /* 解析参数 */ for (j = 4; j < c->argc; j++) { int additional = c->argc-j-1; if (!strcasecmp(c->argv[j]->ptr,"replace")) { replace = 1; } else if (!strcasecmp(c->argv[j]->ptr,"absttl")) { absttl = 1; } else if (!strcasecmp(c->argv[j]->ptr,"idletime") && additional >= 1 && lfu_freq == -1) { if (getLongLongFromObjectOrReply(c,c->argv[j+1],&lru_idle,NULL) != C_OK) return; if (lru_idle < 0) { addReplyError(c,"Invalid IDLETIME value, must be >= 0"); return; } lru_clock = LRU_CLOCK(); j++; /* Consume additional arg. */ } else if (!strcasecmp(c->argv[j]->ptr,"freq") && additional >= 1 && lru_idle == -1) { if (getLongLongFromObjectOrReply(c,c->argv[j+1],&lfu_freq,NULL) != C_OK) return; if (lfu_freq < 0 || lfu_freq > 255) { addReplyError(c,"Invalid FREQ value, must be >= 0 and <= 255"); return; } j++; /* Consume additional arg. */ } else { addReply(c,shared.syntaxerr); return; } }在上边我们提到了restore命令格式,我们可以看到,在第四个参数开始都是可选参数,所以解析参数中是从j=4开始遍历的,在遍历的过程中会根据不同的参数做不同的操作。我们依次来看下这四个命令: ...

October 8, 2019 · 2 min · jiezi

第7课-循环语句

·循环语句的基本工作方式-通过条件表达式判定是否执行循环体-条件表达式遵循if语句表达式的原则 · do,while,for的区别-do语句先执行后判断,循环体至少执行一次-while语句先判断后执行,循环体可能不执行-for语句先判断后执行,相比while更加简洁 · do...while语句的循环方式 · while语句的循环方式 · for语句的循环方式 三种循环语句使用对比例子7-1: include "stdio.h"int f1(int n){ int ret = 0;if (n > 0){ do { ret += n; n--; } while (n > 0);}return ret;} int f2(int n){ int ret = 0;while (n > 0){ ret += n; n --;}return ret;}int f3(int n){ int ret = 0;int i = 0;for(i = 1;i <= n;i++){ ret += i;}return ret;} ...

October 8, 2019 · 1 min · jiezi

第6课-分支语句

if语句分析·if语句用于根据条件选择执行语句·else不能独立存在且总是与它最近的if相匹配不能·else语句后可以接连其他if语句 ·if语句中零值比较的注意点-bool型(bool为布尔型,只有一个字节),取值false和true变量应该出现于条件中,不要进行比较-变量和0值比较时,0值应该出现在比较符号左边-float型变量不能直接进行0值比较,需要定义精度 switch语句分析·switch语句对应单个条件多个分支的情形·case语句分支必须要有break,否则会导致分支重叠·default语句有必要加上,以处理特殊情况(当所有的case都不执行的时候,执行default语句,不要用default处理正常的程序逻辑)·case语句中的值只能是整型或字符型(绝对不能用1.1,1.2,2.3等浮点型)·case语句的排列顺序 -按字母或数字排序各条语句 -正常情况放在前面,异常情况放在后面 -default语句只用于处理真正的默认情况 例子6-1: include "stdio.h"void f1(int i){ if (i < 6){ printf("Failed!\n");}else if ((6 <= i) && (i <= 8)){ printf("Good!\n");}else{ printf("Perfect!\n");}}void f2(char i){ switch (i){case 'c': printf("Compile!\n"); break;case 'd': printf("Debug!\n"); break;case 'o': printf("object!\n"); break;case 'r': printf("Run!\n"); break;default: printf("Unknown!\n"); break;}}int main(){ f1(5);f1(9);f1(7);f2('c');f2('o');f2('d');f2('e');return 0;}输出结果:Failed!Perfect!Good!Compile!object!Debug!Unknown! 例子6-2: include "stdio.h"void f1(int i){ switch (i < 6){ case 1: printf("Failed!\n"); break; default: switch ( (6 <= i) && (i <= 8) ) { case 1: printf("Good!\n"); break; default: printf("Perfect!\n"); break; } break;}}void f2(char i){ ...

October 7, 2019 · 1 min · jiezi

怎样使用U盘安装系统

准备工作一个8G及以上的U盘;软碟通UltraISO,下载地址,非免费,但试用就够了;系统镜像,推荐去MSDN下载; 安装过程利用U盘制作启动盘,准备好上述的东西,然后开始制作启动盘: 注意:制作之前请确保自己U盘中的数据已经备份好,否则U盘数据会被擦除; 进入BIOS设置U盘启动,详情见BIOS设置U盘启动;设置好BIOS后重启机器,开始系统的安装:欢迎关注微信公众号:村雨1943;新文章首发:https://cunyu1943.github.io创作不易,未经同意,转载请注明出处~

October 7, 2019 · 1 min · jiezi

PHP7源码分析初探PHP字符串类型中的引用计数

作者:王澍 背景介绍字符串类型也是我们平时常用的类型,由于字符串的特性,为了节省内存通常相同字符串变量会共用一块内存空间,通过引用计数来标记有多变量使用这块内存。但是,经过GDB追踪发现,并不是所有字符串都是正常在操作引用计数,有正常累加的,有时候为0,又有时候为1。为了一探究竟,于是简单分析了一下各种赋值情况。环境情况系统版本:Ubuntu 16.04.3 LTSPHP版本:PHP 7.1.0gdb版本:GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1一、基础变量PHP中zval是所有变量的基础。(zend_type.h 121行) 其中zend_value存储了具体数据,结构如图: (zend_type.h 101行) zend_value为一个联合体,整体占用8字节。u1为一个联合体,存储了类型所需要的必要数据,占用4字节。u2位一个联合体,存储了一些额外数据,比如hash碰撞时的next,占用4字节。整个zval结构体,占用16字节,就支持了php所有类型。 PHP7中用如此简单而巧妙的zval存储了所有类型数据,那么一个不确定长度的字符串又如何能存储在一个16字节的zval中呢? 二、字符串变量<?php$a = "hello world";echo $a;通过GDB调试可以看到: type = 6,对照类型的定义,可以看到类型是IS_STRING (zend_type.h 303行) 由于我们的字符串长度不一定,所以单靠zval的16个字节是无法直接存储的,于是通过value中的str指向真正存储字符串的内存地址。通过打印我们可以看到,这个地址的类型是zend_string 1、zend_string结构体先看一下它的数据结构,如图 (zend_type.h 169行) zend_string结构体中的gc头部先是gc,可以看一下其他复杂类型,头部都有一个gc,它的作用是什么?看看gc的数据结构,如图: 第一个是refcount,记录了被引用的次数。第二个u是一个联合体,可以看到与zval的u1很像,关键是记录了type。那么作用就比较好猜测了,在程序执行gc或其他操作的时候,对于任意一个复杂类型,指针头部就是gc,里面不光有引用计数,并且能通过u.v.type确定该复杂类型的真正类型。 zend_string结构体中的h从名字可以猜测,这是字符串的hash,空间换时间的思想,把计算好的hash保存下来,提高性能。 zend_string中的len比较明显,它存储了字符串的长度。 zend_string中的val[1]这种写法是c里面的柔性数组,这里存储了整个字符串,通过这个方式保证字符串所在的内存地址是与该结构体内存地址紧密相连的,减少了去另外一个块内存取值的时间。(ps:留个小疑问,gdb就可以追踪到。柔性数组是否占内存空间?zend_string的对齐后是什么结构?整体占多大?) 2、zend_string实际内容了解过结构本身,可以打印一下内容来看看,如图 该地址内存储的确实是hello world,为什么gc中的refcount是0呢? 原因是这样的: 常量字符串,在PHP代码中的固定字符串,在编译阶段存储在全局变量表中,又叫做字面量表,请求结束后才会被销毁,所以refcount一直为0。临时字符串,发生在虚拟机执行opcode时计算出来的字符串,存储在临时变量区,有正常的refcount。修改一下代码,看一下临时字符串 <?php$a = "hello world".time();echo $a;打印一下这个变量的zval,refcount为1,如图 三、字符串的引用计数1.临时字符串直接赋值对于临时字符串,应该是每有一个被赋值的变量时,该zend_string中的引用计数+1,并且在引用计数为0时,释放这块内存。<?php$a = "hello world".time();echo $a;$b = $a;echo $b;当为$a赋值完成时,$a,在栈上第一个位置,类型为6,IS_STRING,取value中的str,地址为:**0x7ffff4402c30**,可以看到内容,zend_string引用计数为1。 当为$b赋值完成时,$b在栈上第二个位置,类型为6,IS_STRING,取value中的str同样地址为:**0x7ffff4402c30**,zend_string引用计数为2。 大致引用情况可以画出: 2.引用赋值对于变量直接赋值,上面已经画出了引用关系,那么如果是引用类型呢?<?php$a = "hello world".time();echo $a;$b = &$a;echo $b;当为$a赋值完成时,$a,在栈上第一个位置,类型为6,IS_STRING,取value中的str,地址为:**0x7ffff4402c30**,可以看到内容,zend_string引用计数为1。 ...

October 6, 2019 · 1 min · jiezi

PAT测试题目-QQ帐户的申请与登陆

梗述与开发环境本文的重点是针对测试点1、测试点2未通过,存在的问题进行总结。编程语言:C语言。数据结构:Hash表。 测试点的问题与解决方法测试点1与测试2:运行时错误。原因:在程序中使用了strcpy函数。解决方法:自己编程实现一个类似于strcpy的函数。 测试点2:N和L指令各一半,随机交错。帐号随机,取到上下界。密码随机,取到上下界。答案错误。原因:若一个字符串的长度为n,则存储它的字符数组的长度至少为n+1。所以存储密码的字符数组大小至少为17。如果设置为16,不能通过本测试点。 其它需要注意的点:字符数组不能仅仅是定义它,还要对它初始化,置空。否则,存放输出结果的字符数组,在打印输出时,会带有一些古怪的尾巴带着输出出来。 参考文献[1] https://zhidao.baidu.com/ques... (C语言试题: 若一个字符串的长度为n,则存储它的字符数组的长度至少为n+1。)[2] https://zhidao.baidu.com/ques... (一个字符数组中包含了一个长度为n的字符串,则该字符串首尾字符的数组下标分别是什么?)[3] https://pintia.cn/problem-set... (QQ帐户的申请与登陆)

October 6, 2019 · 1 min · jiezi

第5课-变量属性

C语言中的变量属性 ·C语言中的变量可以有自己的属性 ·在定义变量的时候可以加上“属性”关键字 ·属性关键字指明变量的特有意义 语法: property type var_name;(属性+类型+变量) auto关键字·auto即C语言中局部变量的默认属性·auto表明将被修饰的变量存储于栈上·编译器默认所有的局部变量是auto的 register关键字·register关键字指明将局部变量 存储于寄存器中·register只是请求寄存器变量,但不一定请求成功·register变量的必须是CPU寄存器可以接受的值·不能用&运算符获取register变量的地址(&取地址符号是获取内存里面的地址,而register是获取请求寄存器)register int g_v;是错误的,register关键字不能声明全局变量,因为全局变量不能长时间占用寄存器printf("0x%08X",&var)也是错误的,取地址是取的内存的地址,不能用取地址符号取寄存器 static关键字·static关键字指明变量的“静态”属性 -static修饰的局部变量存储在程序静态区·static关键同时具有“作用域限定符”的意义 -static修饰的全局变量作用域只是声明的文件中 -static修饰的函数作用域只是声明的文件中 例子5-1: include "stdio.h"int F1(){ int r = 0;r++;return r;}int F2(){ static int r = 0; r++;return r;}int main(){ auto int i = 0; //显示声明auto属性,i为栈变量static int k = 0; //局部变量k的存储区位于静态区,作用域位于main中register int j = 0; //向编译器申请j存储于寄存器中printf("%p\n",&i);printf("%p\n",&k);// printf("%pn", &j); //程序会报错,寄存器不能使用地址符号,地址符号针对于内存 for (i = 0; i < 5; i++){ printf("%d\n",F1());}for (i = 0; i < 5; i++){ printf("%d\n", F2());}}输出结果:004FFA6000E9A1801111112345结果分析:很明显局部变量i,k的内存不是连续的,因为局部变量k是存储在静态区中,所二者地址不是连续的,如果把static去掉,二者的地址是连续的 ...

October 6, 2019 · 1 min · jiezi

第4课-类型转换

·C语言中的数据类型可以进行转换1.强制类型转换2.隐式类型转换 ·强制类型转换的语法-(Type)var_name;-(Type)value ·强制类型转换的结果-目标类型能够容纳的目标值:结果不变-目标类型不能容纳的目标值:结果将产生截断(比方说int类型转化为char类型,舍弃高3字节,留下低1字节) ·浮点数的强制类型转换的结果保留整数部分,去掉小数部分。int a = (int)3.1415的结果为3; 注意:不是所有的强制类型转换都能成功,当不能进行强制类型转换时,编译器将报错。 例子4-1: include"stdio.h"struct TS{ int i;int j;};struct TS ts;int main(){ short s = 0x1122;char c = (char)s; //22int i = (int)s; int j = (int)3.1415;unsigned int p = (unsigned int)&ts; //结构体地址,不会报错//long l = (long)ts; //结构体类型不能转化为基本类型,C语言中是不允许的//ts = (struct TS)l; //基本类型也不能强制类型转化为结构体类型printf("s = %x\n", s);printf("c = %x\n", c);printf("i = %x\n", i);printf("j = %x\n", j);printf("p = %x\n", p);printf("ts = %p\n", &ts);return 0;}输出结果:s = 1122c = 22i = 1122j = 3p = efa190&ts = 00EFA190 ...

October 6, 2019 · 1 min · jiezi

取消宏定义

今天遇到一个问题是:重复宏定义。想到的解决方法是使用undef取消该重复宏。解决方法#ifdef xxx #undef xxx#endif或 #ifndef xxx #define xxx#endif

October 5, 2019 · 1 min · jiezi

Redis5源码学习浅析redis命令之rename篇

baiyan 命令语法命令含义:将 key改名为newkey命令格式: RENAME key newkey命令实战: 127.0.0.1:6379> keys *1) "kkk"2) "key1"127.0.0.1:6379> rename kkk key1OK127.0.0.1:6379> keys *1) "key1"127.0.0.1:6379> rename kkk kkk(error) ERR no such key返回值: 改名成功时提示 OK ,失败时候返回一个错误 源码分析主要流程rename命令的处理函数是renameCommand(): void renameCommand(client *c) { renameGenericCommand(c,0);}renameCommand()函数调用了底层通用重命名函数: void renameGenericCommand(client *c, int nx) { robj *o; long long expire; int samekey = 0; // 重命名之前和之后的键名相同,置samekey标志为1,不做处理 if (sdscmp(c->argv[1]->ptr,c->argv[2]->ptr) == 0) samekey = 1; // 如果重命名之前的键不存在,直接返回 if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr)) == NULL) return; // 如果置了samekey标志为1,代表重命名前后的键名相同,那么什么都不做,直接返回OK if (samekey) { addReply(c,nx ? shared.czero : shared.ok); return; } incrRefCount(o); // 由于查找到了o,引用计数++ expire = getExpire(c->db,c->argv[1]); // 获取重命名之前键的过期时间 if (lookupKeyWrite(c->db,c->argv[2]) != NULL) { // 如果重命名之后的键已经存在 if (nx) { // 是否是执行的renamenx命令 decrRefCount(o); addReply(c,shared.czero); return; } /* 重命名之后的键已经存在,需要删除这个已存在的键 */ dbDelete(c->db,c->argv[2]); } dbAdd(c->db,c->argv[2],o); // 到这里重命名之后的键一定不存在了,可以添加这个键 if (expire != -1) setExpire(c,c->db,c->argv[2],expire); // 如果之前设置了过期时间,同样给新键设置过期时间 dbDelete(c->db,c->argv[1]); // 新键创建完毕,需删除之前的键 signalModifiedKey(c->db,c->argv[1]); // 发出修改键信号 signalModifiedKey(c->db,c->argv[2]); // 发出修改键信号 notifyKeyspaceEvent(NOTIFY_GENERIC,"rename_from", c->argv[1],c->db->id); // 键空间事件触发 notifyKeyspaceEvent(NOTIFY_GENERIC,"rename_to", c->argv[2],c->db->id); // 键空间事件触发 server.dirty++; addReply(c,nx ? shared.cone : shared.ok);}我们首先整理一下这个命令的思路,如果让我们自己去实现重命名一个键这个命令,应该怎么做呢?首先我们会判断一些边界条件。我们知道,键是放在键空间字典里的。假设我们现在有key1-value1和key2-value2。现在需要把key1重命名为key2,即重命名之后生成值为key2-value1的键值对。考虑以下边界情况: ...

October 4, 2019 · 1 min · jiezi

墨尔本大学comp10002课业解析

墨尔本大学comp10002课业解析 HHappyyy简书作者2019-10-04 15:22打开App题意: 可视化网格路径,检测路径合法性,支持修复非法路径 解析: 第一阶段要求完成读取地图和分析数据功能。 上面的地图输入格式如下: 第一行代表地图行列,第二行和第三行分别表示出发点和终点,第四行至表示障碍, 表示障碍,表示障碍,标志地图信息加载完毕,$至最后一行表示路径信息。 该阶段要打印如下内容: 其中最后一行依据路径状态打印5条语句之一。 若路径起始点和地图出发点不同,打印Initial cell in the route is wrong! 若路径终点和地图终点不同,打印Goal cell in the route is wrong! 若每次移动超过两格,打印There is an illegal move in this route! 若路径上有障碍。打印There is a block on this route! 其它情况下打印The route is valid! 第二阶段要求把地图和路径可视化(使用ASCII码),以及遇到障碍重新寻路。 第三阶段展示路径修复的全过程。 涉及知识点: 动态内存、路径规划、数据结构(数组、链表等) 更多可+V讨论g19963812037

October 4, 2019 · 1 min · jiezi

通过VC6直观地看一看C语言的栈管理

前言VC6 虽然已经是几乎被淘汰的技术,但是它在调试时允许我们直观地查看寄存器和内存空间中的值和地址,转换为汇编语言后每条指令在内存空间的地址的特性,可以让我们更直观地看到一些操作。 本文希望通过 VC6 更直观一些地看到C语言在为局部变量和函数调用分配内存空间的具体细节,从而验证从书上学到的知识。相关知识主要参考《深入理解计算机系统》(中文第二版)第三章,其中3.1节和3.7节尤为重要。 代码: #include<stdio.h>void functionA(int, double);int main(){ int i = 1; double d = 8.0; functionA(i,d); short int s = 10; return 0;}void functionA(int i, double d){ int fa_i = i; double fa_d=d;}VC6调试视角: 正文main过程的局部变量7: int i = 1;00401038 mov dword ptr [ebp-4],18: double d = 8.0;0040103F mov dword ptr [ebp-0Ch],000401046 mov dword ptr [ebp-8],40200000h解读:首先要明确一下这些汇编指令中的取值规则。ebp:取出ebp中存储的值(一个地址)作为取来用的值。[ebp]:取出ebp中存储的值(一个地址)后,取出改值(地址)指向的内存空间中存储的值作为用的值。因此mov dword ptr [ebp-4],1,要求将1存入ebp-4这个地址对应的内存空间。留意一下此时的寄存器和内存窗口:执行完00401038 mov dword ptr [ebp-4],1后那么对于 ...

October 4, 2019 · 1 min · jiezi

Redis5源码学习浅析redis命令之persist篇

Grape 命令语法命令含义:移除给定key的生存时间,将这个 key 从『易失的』(带生存时间 key )转换成『持久的』(一个不带生存时间、永不过期的 key )。命令格式: PERSIST key命令实战: redis> SET mykey "Hello"OKredis> EXPIRE mykey 10(integer) 1redis> TTL mykey(integer) 10redis> PERSIST mykey(integer) 1redis> TTL mykey(integer) -1redis> 返回值: 当生存时间移除成功时,返回 1 . 如果 key 不存在或 key 没有设置生存时间,返回 0 . 源码分析命令入口:/* PERSIST key */void persistCommand(client *c) { //查找这个key,调用lookupKeyWrite函数 if (lookupKeyWrite(c->db,c->argv[1])) { //如果这个key存在,调用删除函数,调用dictGenericDelete函数 if (removeExpire(c->db,c->argv[1])) { addReply(c,shared.cone); server.dirty++; } else { addReply(c,shared.czero); } } else { addReply(c,shared.czero); }}此处是persist命令的入口,我们可以看到这个命令在执行的过程中有一下几个阶段:1.查找你要执行persist命令的key,看是否存在,如果不存在则直接返回客户端信息。如果存在,这调用删除函数移除过期时间,删除成功返回给客户端成功信息,aof标志加1。 判断key的可用性/* Lookup a key for write operations, and as a side effect, if needed, expires * the key if its TTL is reached. * * Returns the linked value object if the key exists or NULL if the key * does not exist in the specified DB. *//* 查找这个key*/robj *lookupKeyWrite(redisDb *db, robj *key) { //首先查找这个key是否过期,过期则删除,此函数在之前的几篇文章中已经介绍过,此处不在赘述。 expireIfNeeded(db,key); 在上一个步骤的基础上,如果此键被删除则返回null,否则返回这个键的值 return lookupKey(db,key,LOOKUP_NONE);}int removeExpire(redisDb *db, robj *key) { /* An expire may only be removed if there is a corresponding entry in the * main dict. Otherwise, the key will never be freed. */ serverAssertWithInfo(NULL,key,dictFind(db->dict,key->ptr) != NULL); return dictDelete(db->expires,key->ptr) == DICT_OK;}这个过程是在判断这个key是否存在,它分为两个部分,首先他会查找这个key是否过期,如果过期则删除,然后去查询这个key,反之直接查找这个key,此处又一个疑问,为什么不是先查找这个key是否存在再去判断是否过期?我的想法:redis中存储过期时间和key-value是两个字典,在expire字典中存在的值一定在key-value字典中存在,那么在expire过期的时候就涉及reids的惰性删除机制,为了满足这个机制,reids在设计的时候会先对key的过期时间做判断,然后再去判断这个key是否存在,此时如果对redis的删除机制感兴趣的话,请查阅关于Redis数据过期策略。 ...

October 3, 2019 · 2 min · jiezi

TinyHttp源码分析

主函数1.服务器端初始化: 创建socket => 设置端口复用 => 绑定socket与服务器地址 => 如果未指定端口,动态分配 => 监听int on = 1;unsigned int port = 4000;struct sockaddr_in name;int lfd = socket(PF_INET, SOCK_STREAM, 0); //创建socketmemset(&name, 0, sizeof(name)); //初始化namename.sin_family = AF_INET;name.sin_port = htons(*port);name.sin_addr.s_addr = htonl(INADDR_ANY);setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) //端口复用bind(lfd, (struct sockaddr *)&name, sizeof(name))if (*port == 0) { //动态分配端口 socklen_t namelen = sizeof(name); getsockname(lfd, (struct sockaddr *)&name, &namelen); *port = ntohs(name.sin_port);}listen(httpd, 5);2.服务器阻塞等待客户端连接,一旦连接,开辟一个线程并执行对应响应函数: while(1) { client_sock = accept(server_sock, (struct sockaddr*)&client_name, &client_name_len); printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &client_name.sin_addr, str, sizeof(str)), ntohs(client_name.sin_port)); pthread_create(&newthread , NULL, (void *)accept_request, (void *)(intptr_t)client_sock);}响应函数1.获取请求方法: ...

October 2, 2019 · 2 min · jiezi

CMake指南

版权申明:本文原创首发于以下网站,您可以自由转载,但必须加入完整的版权声明博客园:https://www.cnblogs.com/Mogoo... csdn博客:https://blog.csdn.net/nmjkl001/ 知乎:https://www.zhihu.com/people/... 简书:https://www.jianshu.com/u/954... segmentfault:https://segmentfault.com/u/mo...CMakeLists.txt 文件解析cmake verson,指定cmake版本 cmake_minimum_required(VERSION 3.2)project name,指定项目的名称,一般和项目的文件夹名称对应 PROJECT(test_sqrt)head file path,头文件目录 INCLUDE_DIRECTORIES( include )source directory,源文件目录 AUX_SOURCE_DIRECTORY(src DIR_SRCS)set environment variable,设置环境变量,编译用到的源文件全部都要放到这里,否则编译能够通过,但是执行的时候会出现各种问题,比如"symbol lookup error xxxxx , undefined symbol" SET(TEST_MATH ${DIR_SRCS} )add executable file,添加要编译的可执行文件 ADD_EXECUTABLE(${PROJECT_NAME} ${TEST_MATH})add link library,添加可执行文件所需要的库,比如我们用到了libm.so(命名规则:lib+name+.so),就添加该库的名称 TARGET_LINK_LIBRARIES(${PROJECT_NAME} m)编译阶段make 命令默认执行的是 make all,make all 有四个阶段: prepare test compile package make prepare : 只会执行prepare阶段,这个阶段主要是下载编译依赖 make test : 只会执行test阶段,这个阶段主要是做单元测试 make compile : 只会执行compile阶段,这个阶段主要是进行编译 make package : 只会执行packege阶段,这个阶段会把编译产出都copy到output目录中 make install : 只会执行install阶段,这个阶段是把编译产出放到$GOPATH/bin目录下,这个不是必须的 make clean : 只会执行clean阶段,这个阶段把编译输出的一些文件删除,恢复到编译之前的状态基本实践创建项目文件夹hello文件夹hello内,创建构建目录build文件夹hello内,创建源代码(main.cpp)文件夹hello内,创建CMakeLists.txt ...

October 2, 2019 · 1 min · jiezi

string与char-小知识

在C++中,使用字符串相对于char *,我更倾向于使用string。其优点更安全,更多的易用接口。 简化的string类似于下列实现,除了比char 多几个字节的占用空间外几乎一样,但比char 省心很多,这也是C++的"++"表现之一。 struct string { char* data;; size_t length;};

October 2, 2019 · 1 min · jiezi

Redis5源码学习浅析redis命令之randomkey篇

baiyan 命令语法命令含义:从当前选定数据库随机返回一个key命令格式: RANDOMKEY命令实战: 127.0.0.1:6379> keys *1) "kkk"2) "key1"127.0.0.1:6379> randomkey"key1"127.0.0.1:6379> randomkey"kkk"返回值: 随机的键;如果数据库为空则返回nil 源码分析主体流程keys命令对应的处理函数是randomKeyCommand(): void randomkeyCommand(client *c) { robj *key; // 存储获取到的key if ((key = dbRandomKey(c->db)) == NULL) { // 调用核心函数dbRandomKey() addReply(c,shared.nullbulk); // 返回nil return; } addReplyBulk(c,key); // 返回key decrRefCount(key); // 减少引用计数}随机键生成以及过期判断randomKeyCommand()调用了dbRandomKey()函数来真正生成一个随机键: robj *dbRandomKey(redisDb *db) { dictEntry *de; int maxtries = 100; int allvolatile = dictSize(db->dict) == dictSize(db->expires); while(1) { sds key; robj *keyobj; de = dictGetRandomKey(db->dict); // 获取随机的一个dictEntry if (de == NULL) return NULL; // 获取失败返回NULL key = dictGetKey(de); // 获取dictEntry中的key keyobj = createStringObject(key,sdslen(key)); // 根据key字符串生成robj if (dictFind(db->expires,key)) { // 去过期字典里查找这个键 ... if (expireIfNeeded(db,keyobj)) { // 判断键是否过期 decrRefCount(keyobj); // 如果过期了,删掉这个键并减少引用计数 continue; // 当前键过期了不能返回,只返回不过期的键,进行下一次随机生成 } } return keyobj; }}那么这一层的主逻辑又调用了dictGetRandomKey(),获取随机的一个dictEntry。假设我们已经获取到了随机生成的dictEntry,我们随后取出key。由于不能返回过期的key,所以我们需要先判断键是否过期,如果过期了就不能返回了,直接continue;如果不过期就可以返回。 ...

October 1, 2019 · 2 min · jiezi

Binder驱动之死亡通知

在Binder通信建立后,Client端可能需要知道Server端的存活状态。当Server端挂掉时,Client端需要清理与通信相关的数据和行为,这个清理过程就是通过Binder死亡通知机制实现的。 注册死亡通知应用层通过调用BpBinder::linkToDeath()来注册死亡通知。Native Binder通信可以直接调用这个接口,Java通信需要通过Jni来调用。 frameworks/base/core/jni/android_util_Binder.cppstatic void android_os_BinderProxy_linkToDeath(JNIEnv* env, jobject obj, jobject recipient, jint flags) // throws RemoteException{ ...... // 获取BpBinder对象 IBinder* target = (IBinder*) env->GetLongField(obj, gBinderProxyOffsets.mObject); ...... // 只有远程传输需要注册死亡通知 if (!target->localBinder()) { // 获取死亡通知队列 DeathRecipientList* list = (DeathRecipientList*) env->GetLongField(obj, gBinderProxyOffsets.mOrgue); // 创建JavaDeathRecipient对象,将其加入到死亡回收队列 sp<JavaDeathRecipient> jdr = new JavaDeathRecipient(env, recipient, list); // 调用BpBinder::linkToDeath()来建立死亡通知 status_t err = target->linkToDeath(jdr, NULL, flags); ...... } }处理死亡通知的是JavaDeathRecipient,它继承IBinder::DeathRecipient,接收到死亡通知时会回调binderDied()。 frameworks/base/core/jni/android_util_Binder.cppclass JavaDeathRecipient : public IBinder::DeathRecipient{public: JavaDeathRecipient(JNIEnv* env, jobject object, const sp<DeathRecipientList>& list) : mVM(jnienv_to_javavm(env)), mObject(env->NewGlobalRef(object)), mObjectWeak(NULL), mList(list) { ...... // 加入到回收队列 list->add(this); android_atomic_inc(&gNumDeathRefs); // 增加对象引用计数,当创建对象个数达到200时强行出发GC incRefsCreated(env); } void binderDied(const wp<IBinder>& who) { if (mObject != NULL) { JNIEnv* env = javavm_to_jnienv(mVM); // 调用Java的sendDeathNotice方法 env->CallStaticVoidMethod(gBinderProxyOffsets.mClass, gBinderProxyOffsets.mSendDeathNotice, mObject); ...... // 释放全局引用,增加弱引用,以便GC回收 mObjectWeak = env->NewWeakGlobalRef(mObject); env->DeleteGlobalRef(mObject); mObject = NULL; } }Java的注册过程最终也是指向到BpBinder中。死亡通知的注销也同样在这里,一起看一下。 ...

September 30, 2019 · 4 min · jiezi

21个入门练手项目让你轻松玩转C语言

整理了 21 个 C 语言练手项目,从基础语法开始,逐步深入,通过一个个练手项目,让你轻松驰骋在 C 语言的快车道。不走弯路就是捷径! 1.《C语言入门教程》:引入大量的 C 语言程序案例,把算法和语法结合起来,通过引导大家由浅入深地编写 C 程序,让大家掌握 C 语言。将从中学会 C 语言语法、数组、模块化程序设计指针、文件的输入与输出等。 2.《C语言实现文件类型统计程序》:使用C语言实现一个文件类型统计程序。 3.《C 语言实现多线程排序》:使用 C 语言多线程模型实现了排序算法。 4.《3个C语言实例带你掌握递归方法论》:通过 3 个 C 语言编程实例,让你在利用递归解决实际问题的过程中学习递归并掌握其核心思想。 5.《C语言实现LRU缓存》:使用 C 语言实现 LRU 缓存,从中学习 LRU 缓存的基本概念、C 语言相关编程技巧,双向链表的 C 语言实现以及哈希表的 C 语言实现。6.《C语言实现通讯录》:使用 C 语言完成一个简单的通讯录。会涉及到结构体、数组、链表等重要概念。 7.《C 语言制作简单计算器》:用 C 语言做一个简单的计算器,进行加、减、乘、除操作。学习 C 语言的基本语法,与输入与输出。 8.《C语言实现大数计算器(加减乘除)》:通过 C 语言实现一个简易计算器,用于解决任意长度的有符号整数的加、减、乘、除运算。将利用全新的数据结构来表示大数,并将新数据结构存储的大数转化为字符用于显示。以及基于大数新的数据结构完成加、减、乘、除的计算算法设计。 9.《C 语言实现聊天室软件》:使用 C 语言实现聊天室软件,学习并实践基本的 Linux socket 通信的相关技术,加深对 TCP/IP 协议栈的理解。 10.《C 语言实现 web 服务器》:使用 C 语言实现一个Web服务器。将会学习 C 语言网络开发,epoll IO 复用机制,熟悉Linux下的C语言程序编译方法,Makefile编写等技能。 ...

September 20, 2019 · 1 min · jiezi

redis5源码分析浅析redis命令之del篇

baiyan 命令语法命令格式: DEL [key1 key2 …]命令实战: 127.0.0.1:6379> del key1(integer) 1返回值:被删除 key 的数量 源码分析首先我们开启一个redis客户端,使用gdb -p redis-server的端口。由于del命令对应的处理函数是delCommand(),所以在delCommand处打一个断点,然后在redis客户端中执行以下几个命令: 127.0.0.1:6379> set key1 value1 EX 100OK127.0.0.1:6379> get key1"value1"127.0.0.1:6379> del key1首先设置一个键值对为key1-value1、过期时间为100秒的键值对,然后在100秒之内对其进行删除,执行del key1,,删除这个还没有过期的键。我们看看redis服务端是如何执行的: Breakpoint 1, delCommand (c=0x7fb46230dec0) at db.c:487487 delGenericCommand(c,0);(gdb) sdelGenericCommand (c=0x7fb46230dec0, lazy=0) at db.c:468468 void delGenericCommand(client *c, int lazy) {(gdb) n471 for (j = 1; j < c->argc; j++) {(gdb) p c->argc $1 = 2(gdb) p c->argv[0]$2 = (robj *) 0x7fb462229800(gdb) p *$2$3 = {type = 0, encoding = 8, lru = 8575647, refcount = 1, ptr = 0x7fb462229813}我们看到,delCommand()直接调用了delGenericCommand(c,0),貌似是一个封装好的通用删除函数,我们s进去,看下内部的执行流程。 ...

September 20, 2019 · 2 min · jiezi

河北专接本一些考题

最近在attack专接本,分享一些题目类型与我的生活自己的时间表:早晨是记忆力最好的时段,背英语单词,作文上午期间头脑比较清醒,选择一些需要理解,分析的内容来复习,---阅读,数学等等12:00-14:00 中午如果不睡觉,精力不济,这两个小时留给午饭和午休下午会根据当前复习效果来安排,选择薄弱或进度较缓慢的科目复习5:00-6:00 适合晚饭和遛弯-缓解压力晚上精神较为集中,攻克难题较佳 微机原理8086CPU寻址方式如果使用的是基地bx那么寄存器编制是ds寄存器寻址直接寻址间接寻址加变址 C语言s=a+aa+aaa+a(n个a的值,其中a是一数字,n表示a的位数) int main(){ int a,n; int t,s = 0,i = 1; printf("please input a and n"); scanf("%d%d",&a,&n); while(i<n){ t = t+a; s = s+t; a = a*10; ++i; } printf("%d%d",a,n,s);}//一篇文章,一共3行文字,每行有80个字符,分别统计中文大小写,数字,空格以及其他字符个数。 #include <stdio.h>#include <stdlib.h>#include <math.h>#include <string.h>#include <malloc.h>#define error 1#define ok 0int main(int argc, char const *argv[]){ /* code */ char c[3][80];// 1篇文章 int cap = 0,xiao = 0,num = 0,black = 0,other = 0; printf("please input a symbol\n"); int i,j; for(i = 0;i<3;i++) { gets(c[i]); for(j = 0; j<80&& c[i][j]!='\0' ;j++) { if (text[i][j]>='A'&& text[i][j]<='Z') upp++; else if (text[i][j]>='a' && text[i][j]<='z') low++; else if (text[i][j]>='0' && text[i][j]<='9') dig++; else if (text[i][j]==' ') spa++; else oth++; } } return 0;}递归:一个函数能调用自己吗?n的阶乘int jie(int n){ ...

September 19, 2019 · 1 min · jiezi

Python-的整数与-Numpy-的数据溢出

某位 A 同学发了我一张截图,问为何结果中出现了负数? 看了图,我第一感觉就是数据溢出了。数据超出能表示的最大值,就会出现奇奇怪怪的结果。 然后,他继续发了张图,内容是 print(100000*208378),就是直接打印上图的 E[0]*G[0],结果是 20837800000,这是个正确的结果。 所以新的问题是:如果说上图的数据溢出了,为何直接相乘的数却没有溢出? 由于我一直忽视数据的表示规则(整型的上限是多少?),而且对 Numpy 了解不多,还错看了图中结果,误以为每一个数据都是错误的,所以就解答不出来。 最后,经过学习群里的一番讨论,我才终于明白是怎么回事,所以本文把相关知识点做个梳理。 在正式开始之前,先总结一下上图会引出的话题: Python 3 中整数的上限是多少?Python 2 呢?Numpy 中整数的上限是多少?出现整数溢出该怎么办?关于第一个问题,先看看 Python 2,它有两种整数: 一种是短整数,也即常说的整数,用 int 表示,有个内置函数 int()。其大小有限,可通过sys.maxint() 查看(取决于平台是 32 位还是 64 位)一种是长整数,即大小无限的整数,用 long 表示,有个内置函数 long()。写法上是在数字后面加大写字母 L 或小写的 l,如 1000L当一个整数超出短整数范围时,它会自动采用长整数表示。举例,打印 2**100 ,结果会在末尾加字母 L 表示它是长整数。 但是到了 Python 3,情况就不同了:它仅有一种内置的整数,表示为 int,形式上是 Python 2 的短整数,但实际上它能表示的范围无限,行为上更像是长整数。无论多大的数,结尾都不需要字母 L 来作区分。 也就是说,Python 3 整合了两种整数表示法,用户不再需要自行区分,全交给底层按需处理。 理论上,Python 3 中的整数没有上限(只要不超出内存空间)。这就解释了前文中直接打印两数相乘,为什么结果会正确了。 PEP-237(Unifying Long Integers and Integers)中对这个转变作了说明。它解释这样做的 目的: 这会给新的 Python 程序员(无论他们是否是编程新手)减少一项上手前要学的功课。Python 在语言运用层屏蔽了很多琐碎的活,比如内存分配,所以,我们在使用字符串、列表或字典等对象时,根本不用操心。整数类型的转变,也是出于这样的便利目的。(坏处是牺牲了一些效率,在此就不谈了) ...

September 10, 2019 · 1 min · jiezi

Qt-5SM4加解密客户端-实例开发-可使用

注: 支持 cbc&ebc 模式;支持任意待加密数据长度(PKCS7Padding);仅支持 ASCII;开发环境:Qt 5.12.1.源代码 & 可执行文件 文件:sm4.h /** * \brief SM4 context structure */#ifndef SM4_H#define SM4_H#define SM4_ENCRYPT 0#define SM4_DECRYPT 1#define SM4_RESTART 1#define SM4_KEEP 0#ifdef __cplusplusextern "C" {#endif/** * \brief SM4-CBC buffer encryption/decryption * \param ctx SM4 context * \param dec_en SM4_ENCRYPT or SM4_DECRYPT * \param flag SM4_RESTART or SM4_KEEP * \param iv initialization vector (updated after use) * \param length length of the input data * \param input buffer holding the input data * \param output buffer holding the output data */unsigned int sm4_cbc(unsigned char key[16], int dec_en, int flag, unsigned char iv[16], unsigned int length, unsigned char *input, unsigned char *output );/** * \brief SM4-CBC buffer encryption/decryption * \param ctx SM4 context * \param dec_en SM4_ENCRYPT or SM4_DECRYPT * \param flag SM4_RESTART or SM4_KEEP * \param length length of the input data * \param input buffer holding the input data * \param output buffer holding the output data */unsigned int sm4_ecb(unsigned char key[16], int dec_en, int flag, unsigned int length, unsigned char *input, unsigned char *output);#ifdef __cplusplus}#endif#endif文件:sm4.c ...

September 10, 2019 · 8 min · jiezi

墨尔本大学comp10002Assignment-1课业解析

题意:实现一个根据指令生成特定格式字符的系统 解析:stage1:读取文本中的输入,忽略以"."开头的字符串,以单空格替换文中可能出现的多个空格,并且当输出行字符超过50个字符能够自动换行,在输出行与左边框的距离默认有4个空格。 stage2:扩展stage1的程序,使其能够接受以下的命令.b 忽略之后当前行之后的字符,跳到下一行开始输出.p 打印一个空行,开启一个新的段落.l nn 将与左边框的距离更改为nn.w nn 将每行的最大宽度更改为nn stage3:在stage2的基础上增加指令.c 居中指令,将之后的内容置于当前行的中间.h nn 标题指令,按照nn将后面的内容制作为标题,最高有五级标题 涉及知识点:字符处理,文件读写更多可加微信讨论 微信号:alexa_au

September 10, 2019 · 1 min · jiezi

野子电商数据分析

野子电竞数据官网改版https://www.xxe.io/ 全新登场1.通用型的数据分析入门思维,比如AARRR(海盗模型)获取用户–>提高活跃度–>提高留存率–>获取营收–>自传播 2.实现数据分析的流程深入业务–>构建指标体现–>事件设计–>数据采集–>业务目标–>数据分析–>验证迭代重点:1)构建指标体现;2)数据分析+验证迭代 3.关注的指标1)总体运营指标:1.1.活跃用户数、新增用户数1.2.总订单量、访问到下单转化率1.3.成交金额(GMV)、销售金额、客单价1.4.销售毛利、毛利率 2)流量指标:2.1.新增用户数、页面访问数2.2.用户获取成本2.3.跳出率、页面访问时长、人均页面访问数2.4.注册会员数、活跃会员数、活跃会员率、会员平均购买次数、会员留存率 3)销售指标:3.1.加入购物车次数、加入购物车买家数、加入购物车商品数、购物车支付转化率3.2.下单笔数、下单金额、下单买家数、浏览下单转化率3.3.支付金额、支付买家数、支付商品数、浏览-支付买家转化率、下单-支付买家转化率3.4.交易成功/失败订单数、交易成功/失败金额、交易成功/失败买家数、交易成功/失败商品数、退款订单数量、退款金额、退款率 4)客户价值指标:4.1.累积购买客户数、客单价4.2.新客户数量、新客户获取成本、新客户客单价4.3.消费频率、最近一次购买时间、消费金额、重复购买率 5)市场营销活动指标:5.1.新增访问人数、新增注册人数、总访问次数5.2.订单数量、下单转化率5.3.ROI(投资回报率) 4.如何分析1)渠道分析顺序:曝光–>点击–>下载–>注册–>付费核心数据是有效注册数,其次是付费用户数,最后是arpu值 2)用户运营:2.1.根据活跃度:新增活跃用户、活跃老用户、沉默用户、流失用户…2.2.根据商品偏好:美妆类、母婴类、零食类、电子产品类、书籍类…2.3.根据消费能力:普通会员、黄金会员、白金会员、钻石会员…2.4.根据消费频率:每周一次以上、每月1-2次、三个月2次…

September 9, 2019 · 1 min · jiezi

无边落木萧萧下

** 前言:**为什么要学习 python ?可能因为它是“胶水”吧 ** 开始:**说出来可能不信,我最近在追剧,《老友记》《生活大爆炸》《黑袍纠察队》等等,果然心有多大,这个浪的舞台就有多大,考试的事情要不是有人提醒,已然把它扔到爪哇国了;心中天天带着木有学习的愧疚感,再杂夹着追剧所带来的愉悦,啊,人果然是矛盾的生物啊;但是我又很开心,你说这个事气不气人吧? 看完上述描述,是不是觉得我又习惯性跑题了?其实这种矛盾感在写代码的时候也是常常发生,比如我第一门语言学的是C,但是工作常常用Java;虽然用Java让人觉得写起来很爽(面向对象,你懂的,只是处理业务上的逻辑,其他方面的细节几乎可以忽略掉),但是我的内心是抗拒的; 如果没有对象,那么就 new 一个出来,看似简单平直的想法却带来了很大的性能上的问题,随着对象的日益增多,硬件便很难长久的支撑下去,这个事情就会变得很气人,本来 new 出来一个对象就是为了方便使用的,结果我还是得了解它的生老病死,否则生怕一个不小心就把硬件给撑爆掉;为了解决这种会一不小心撑爆硬件的事情发生,最直白的想法就是少new对象,如果非new不可,那么就new一个足够精简的,所以最后的问题又被转移到如何设计对象上面去了,,,所以,写Java时间久了真的会有一种成就感,你看我就是我那堆代码的上帝,我掌握着它们的生老病死,,,可是上帝也不是那么好当的啊,咱也没那么多精力去维护啊,所以我就觉得写Java就是矛盾感特别严重的一件事情:为了省心,我选择面向对象,但是我选择了面向对象,却再也没省过心,,, 从上述吐槽当中,我的大脑又开了一个脑洞,我觉得设计一门语言绝对是个哲学问题,想要满足所有人的需求是不可能,那么这种不可能是不是可以用某种简化思维来替代,比如:需要对象的时候我用Java,需要性能的时候我用C,需要写的优雅的时候,我就选择ruby?(maybe or not,who care?)所以,我现在想选择 python (hahaha,,,没想到吧,上面提到的我都不要)据说这货已经被纳入高考考试范畴了,这你敢信?但是,事实就是,,,emmmm,怎么说好呢?如果第一门语言学的是python,那么学其他语言要付出更多的辛苦,而且学python不就是为了使用更多的语言吗???(此处手动狗头,仅为一家之言)所以,吐槽完一遍,最后琢磨了一下,还是要有一门特别熟悉的语言作为模板,再辅以《编译原理》等精神食粮以滋养,才能在日新月异的互联网时代生存下去,在不同的事物之间寻找共性是一个比较耗时费力的事情,但是也是比较有成就感的事情吧,,, 所以,今天还是再探索一下 计算机底层 吧,,, hia,hia, hia~~~ 下面是一个实验,可能里面的数字不甚准确,但是意思差不多,领会到就好:注:这个实验是在 2009 年全国信息学奥林匹克冬令营论文 中看到的,作者未知,但是很感谢这位提供实验数据的仁兄 题目:在一堆数字中,找到其中的最大值;分析:最朴素的想法就是循环一遍,找出最大值即可,这样的话这个算法的复杂度主要集中在数字的多少上,设一共有n个数字,则算法复杂度的上限为 O(n) 1.这个时候写一个for循环解法,令输入10000000个数字,每隔100个数字计算一次其平均时钟周期,大概处理一个数字会占到7~8个时钟周期,总的测量周期值为: 75305510 代码如下: int get_max(int* a,int l){ int mx=0,i; for(i=0;i<l;i++){ if(a[i]>mx) mx=a[i]; } return mx;}2.显然,上面这个思想太朴素了,一定可以有个缩减效率的办法,仔细观察会发现 a[i] 出现了两次,实际上就是对 a[i] 做了两次寻址的操作,那么干脆用地址如何? int get_max(int* a,int l){ int mx=0,*ed=a+l; while(a!=ed){ if(*a>mx)mx=*a; a++; } return mx;}这一段代码直接读取 a 的地址,所以理论上效率一定是有所提升的,结果也的确如此,大概处理一个数字会平均占6~7个始终周期,总的测量周期值为: 66047005 3.emmmm,难到这就是结束了吗?好像再怎么从语言语法的层面观察都不能再简化操作了,那应该怎么办?思考一下,一共1千万个数字,上述操作是进行了一遍循环,如果我同时分成8个线路,同时进行寻找最大值的操作会不会更快一些? int get_max(int* a,int l){ assert(l%8==0); #define D(x) mx##x=0 int D(0),D(1),D(2),D(3), D(4),D(5),D(6),D(7),*ed=a+l;#define CMP(x) if(*(a+x)>mx##x)mx##x=*(a+x); while(a!=ed){ CMP(0);CMP(1); CMP(2);CMP(3); CMP(4);CMP(5); CMP(6);CMP(7); a+=8; }#define CC(x1,x2) if(mx##x1>mx##x2)mx##x2=mx##x1; CC(1,0);CC(3,2); CC(5,4);CC(7,6); CC(2,0);CC(6,4); CC(4,0); return mx0;}总的测量周期值为: 34818706看起来效果很显著,比第一个想法的时钟周期缩短了近一半,但是有个疑问,只不过是单路运行转成了8路运行而已,然后速度就快了一倍,是不是有些太夸张了?那么这里如何解释呢?简单的讲,在最初两个程序中,每次计算新的 mx 都会依赖于上一步的计算结果,相关的计算指令也必须依次运行,而将求值过程分为多路处理,mx0,mx1 等变量的相关指令之间互相没有关联,让处理器有更大的机会将他们并发。 ...

September 8, 2019 · 2 min · jiezi

韦东山一期视频学习笔记Norflash

uboot下载这里使用uboot-2015对应的编译器用arm-linux-gcc-4.4.3uboot-2015arm-linux-gcc-4.4.3 编译make mini2440_defconfigmake CROSS_COMPILE=/root/git/arm-linux-gcc-4.4.3/bin/arm-linux-烧写到Norflash运行

September 8, 2019 · 1 min · jiezi

gcc编译参数

编译 gcc -c main.c ==== 编译不链接,生成.o目标文件 gcc -E main.c ==== 预处理 gcc -S main.c ==== 只编译不汇编 gcc -g main.c -o main_d ==== 可进行gdb调试 gcc -Dname='xinzhu' === 定义宏 define name 'xinzhu' gcc main.c -o main -I../path gcc main.c -o main -I../path -L../path gcc -I [大写字母i]寻找头文件目录 /usr/local/include gcc -L [大写字母l]寻找库文件 /usr/local/lib gcc -l word [小写字母l], 寻找动态链接库文件libword.so静态库 .a结尾 # 创建.o目标文件 gcc -c test.c -o libtest.o #创建libtest.a静态库 ar rcs libtest.a libtest.o #链接静态库 gcc -o test main.c -ltest动态库 .so结尾 # 使用位置无关代码创建目标文件 gcc -c -fPIC test.c -o test.o # 创建共享库libtest.so gcc -shared -o libtest.so test.o # 链接静态库 gcc -o test main.c -ltest

September 8, 2019 · 1 min · jiezi

缓冲区溢出栈溢出

前言在现在的网络攻击中,缓冲区溢出方式的攻击占据了很大一部分,缓冲区溢出是一种非常普遍的漏洞,但同时,它也是非常危险的一种漏洞,轻则导致系统宕机,重则可导致攻击者获取系统权限,进而盗取数据,为所欲为。 其实缓冲区攻击说来也简单,请看下面一段代码: void main(int argc, char *argv[]) { char buffer[8]; if(argc > 1) strcpy(buffer, argv[1]);}当我们在对argv[1]进行拷贝操作时,并没对其长度进行检查,这时候攻击者便可以通过拷贝一个长度大于8的字符串来覆盖程序的返回地址,让程序转而去执行攻击代码,进而使得系统被攻击。 本篇主要讲述缓冲区溢出攻击的基本原理,我会从程序是如何利用栈这种数据结构来进行运行的开始,试着编写一个shellcode,然后用该shellcode来溢出我们的程序来进行说明。我们所要使用的系统环境为x86_64 Linux,我们还要用到gcc(v7.4.0)、gdb(v8.1.0)等工具,另外,我们还需要一点汇编语言的基础,并且我们使用AT&T格式的汇编。 就我个人而言,作为一个新手,我还是比较怂来写这篇文章而言的,如果你发现又任何的错误或者不恰当的地方,欢迎指出,希望这篇文章对您有帮助。 进程在现代的操作系统中,进程是一个程序的运行实体,当在操作系统中运行一个程序是,操作系统会为我们的程序创建一个进程,并给我们的程序在内存中分配运行所需的空间,这些空间被称为进程空间。进程空间主要有三部分组成:代码段,数据段和栈段。如下图所示: 栈栈是一种后入先出的数据结构,在现代的大多数编程语言中,都使用栈这种数据结构来管理过程之间的调用。那什么又是过程之间的调用呢,说白了,一个函数或者一个方法便是一个过程,而在函数或方法内部调用另外的过程和方法便是过程间的调用。我们知道,程序的代码是被加载到内存中,然后一条条(这里指汇编)来执行的,而且时不时的需要调用其他的函数。当一个调用过程调用一个被调用过程时,所要执行的代码所在的内存地址是不同的,当被调用过程执行完后,又要回到调用过程继续执行。调用过程调用被调用过程时,需要使用call指令,并在call指令后指明要调用的地址,例如call 地址,当被调用过程返回时,使用ret指令来进行返回,但是并不需要指明返回的地址。那么程序是怎么知道我们要返回到什么地方呢?则主要是栈的功劳:执行call指令时,程序会自动的将call的下一条指令的地址加入到栈中,我们叫做返回地址。当程序返回时,程序从栈中取出返回地址,然后使程序跳转到返回地址处继续执行。 另外,程序在调用另一个过程时需要传递的参数,以及一个过程的局部变量(包括过程中开辟的缓冲区)都要分配在栈上。可见,栈是程序运行必不可少的一种机制。 但是,聪明的你可能一想:不对,既然程序的返回地址保存在栈上,过程的参数以及局部变量也保存在栈上,我们可以在程序中操纵参数和局部变量,那么我们是否也能操作返回地址,然后直接跳转到我们想要运行的代码处呢?答案当然是肯定的。 改变程序的返回地址我们看这也一个程序。 example.cvoid func() { long *res; res = &res + 2; *res += 7;}void main() { int x = 1; func(); x = 0; printf("%d\n", x);}我们在shell中使用如下命令编译运行一下,对于gcc编译时所用的参数,我们先卖个关子。 $ `gcc -fno-stack-protector example.c -o example`$ ./example你或许会说:“哎呀呀,不用看了,这么简单,运行结果是0嘛”。但结果真的是这样嘛。其实,这个程序的运行结果是1。“什么,这怎么可能是1嘛,不得了不得了” 还记的我们提到的我们可以在程序中改变过程的返回地址吗?在func中,看是是对res进行了一些无意义的操作,但是这却改变了func的返回地址,跳过了x = 0这条赋值命令。让我们从汇编的层面上看一下这个程序是如何执行的。 $ gdb gdb exampleGNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-gitCopyright (C) 2018 Free Software Foundation, Inc....gdb-peda$ disassemble func Dump of assembler code for function func: 0x000000000000064a <+0>: push %rbp 0x000000000000064b <+1>: mov %rsp,%rbp 0x000000000000064e <+4>: lea -0x8(%rbp),%rax 0x0000000000000652 <+8>: add $0x10,%rax 0x0000000000000656 <+12>: mov %rax,-0x8(%rbp) ... 0x000000000000066e <+36>: retq End of assembler dump.在gdb中,我们使用disassemble func来查看一下func函数的汇编代码,在这里,程序栈上的情况是这样的,其中栈的宽度为8字节: ...

September 8, 2019 · 4 min · jiezi

浮点数与十六进制互相转换

利用强制转换类型实现。浮点数转十六进制实现:float f = 123.45f;unsigned char *hex = (unsigned char *)&f;打印输出:for(int i = 0; i < 4; i++) printf("0x%02X ", hex[i]); printf("\n");十六进制转浮点数实现:unsigned char hex[] = { 0x66, 0xE6, 0xF6, 0x42 };float f = *(float *)hex;打印输出:printf("%g\n", f); // %g参数按浮点精度四舍五入去除多余的0.

September 8, 2019 · 1 min · jiezi

makefile

执行流程1.读入主Makefile (主Makefile中可以引用其他Makefile)2.读入被include的其他Makefile2.初始化文件中的变量4.推导隐晦规则, 并分析所有规则5.为所有的目标文件创建依赖关系链6.根据依赖关系, 决定哪些目标要重新生成7.执行生成命令内置环境变量MAKECMDGOALS 变量记录了命令行参数指定的终极目标列表例: make hello world $(MAKECMDGOALS) = hello world= ?= :=var1 = value1 最基本的赋值, 不立即展示变量var2 := $(value2 )立即展开变量, := 只能使用前面定义好的变量, = 可以使用后面定义的变量var3 ?= value3 只有在该变量为空时才设置值var4 += value4 将值追加到变量的尾端错误抑制符 @ -不用前缀, 输出执行的命令以及命令执行的结果, 出错的话停止执行前缀 @, 只输出命令执行的结果, 出错的话停止执行前缀 -, 命令执行有错的话, 忽略错误, 继续执行 例: @echo $(name)$@ $^ $< $?$@ 表示目標文件$^ 表示所有依赖文件$+ 表示所有依赖(文件包含重复)$< 表示第一个依赖文件$? 表示比目标还要新的依赖文件列表wildcard pattern placeholder src := a.c b.c $(wildcard *.c) 匹配所有后缀.c文件 $(src: .c=.c) 替换文件后缀.c为.o 模式佔位符匹配规则: 目标和依赖文件名必须一样, 即a.c, a.c %.o : %.c $(CC) -c $< -o $@.PHONY 伪目标 .PHONY: clean clean: rm -rf *findstring$(findstring <find>,<in>) #函数功能: 在字符串<in>中查找<find>字符串#返回:如果找到,那么返回<find>,否则返回空字符串。filter$(filter pattern…,text)#函数功能: 过滤掉字串"TEXT"中所有不符合模式“PATTERN”的单词, 保留所有符合此模式的单词. 可以使用多个模式, 模式中一般需要包含模式字符"%"#返回值: "TEXT"字串中所有符合模式"PATTERN"的字串words$(words <text>)#函數功能: 统计中字符串中的单词个数#返回值: 返回中的单词数strip$(strip <string>)功能:去掉<string>字串中开头和结尾的空字符。返回:返回被去掉空格的字符串值

September 7, 2019 · 1 min · jiezi

Phper-学-C-兴趣入门-字符串-为什么-php-手册里经常说某个函数是二级制安全的

引子为什么 php 手册里经常说某个函数是二级制安全的?我们平常使用函数的时候也没发现有什么区别呀,那么二进制安全到底是什么意思呢? Php 实验<?phpecho strlen("abc"); // 3echo strlen("abc\0"); // 4echo strlen("abc\0d"); // 5echo strlen("abc\0def"); // 7从上面的规律可以看出\0被认为是一个字符,其实在上面的式子中\0是一个ascii字符。 补课简单说明下 ascii 码 我们知道,计算机内部,所有信息最终都是一个二进制值。每一个二进制位(bit)有0和1两种状态,因此八个二进制位就可以组合出256种状态,这被称为一个字节(byte)。也就是说,一个字节一共可以用来表示256种不同的状态,每一个状态对应一个符号,就是256个符号,从`00000000到11111111。 上个世纪60年代,美国制定了一套字符编码,对英语字符与二进制位之间的关系,做了统一规定。这被称为 ASCII 码,一直沿用至今。 man asciiascii 码前 33 个都是控制字符,比如我们最熟悉的换行,都是不可见的字符。 Oct Dec Hex Char Oct Dec Hex Char ──────────────────────────────────────────────────────────────────────── 000 0 00 NUL '\0' 100 64 40 @ 001 1 01 SOH (start of heading) 101 65 41 A 002 2 02 STX (start of text) 102 66 42 B 003 3 03 ETX (end of text) 103 67 43 C 004 4 04 EOT (end of transmission) 104 68 44 D 005 5 05 ENQ (enquiry) 105 69 45 E 006 6 06 ACK (acknowledge) 106 70 46 F 007 7 07 BEL '\a' (bell) 107 71 47 G 010 8 08 BS '\b' (backspace) 110 72 48 H 011 9 09 HT '\t' (horizontal tab) 111 73 49 I 012 10 0A LF '\n' (new line) 112 74 4A J从上面的表中可以看到八进制012和十六进制0A都表示换行 ...

September 7, 2019 · 2 min · jiezi

修改系统时间导致semtimedwait函数一直阻塞的问题解决和分析

修改系统时间,导致sem_timedwait 一直阻塞的问题解决和分析介绍最近修复项目问题时,发现当系统时间往前修改后,会导致sem_timedwait函数一直阻塞。通过搜索了发现int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);传入的第二个阻塞时间参数是绝对的时间戳,那么该函数是存在缺陷的。 sem_timedwait存在的缺陷的理由: 假设当前系统时间是1565000000(2019-08-05 18:13:20),sem_timedwait传入的阻塞等待的时间戳是1565000100(2019-08-05 18:15:00),那么sem_timedwait就需要阻塞1分40秒(100秒),若在sem_timedwait阻塞过程中,中途将系统时间往前修改成1500000000(2017-07-14 10:40:00),那么sem_timedwait此时就会阻塞2年多! 这就是sem_timedwait存在的缺陷!! sem_timedwait函数介绍int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);如果信号量大于0,则对信号量进行递减操作并立马返回正常如果信号量小于0,则阻塞等待,当阻塞超时时返回失败(errno 设置为 ETIMEDOUT)第二个参数abs_timeout 参数指向一个指定绝对超时时刻的结构,这个结果由自 Epoch,1970-01-01 00:00:00 +0000(UTC) 秒数和纳秒数构成。这个结构定义如下 struct timespec { time_t tv_sec; /* 秒 */ long tv_nsec; /* 纳秒 */};解决方法可以通过sem_trywait + usleep的方式来实现与sem_timedwait函数的类似功能,并且不会发生因系统时间往前改而出现一直阻塞的问题。 sem_trywait函数介绍函数 sem_trywait()和sem_wait()有一点不同,即如果信号量的当前值为0,则返回错误而不是阻塞调用。错误值errno设置为EAGAIN。sem_trywait()其实是sem_wait()的非阻塞版本。 int sem_trywait(sem_t *sem)执行成功返回0,执行失败返回 -1且信号量的值保持不变。 sem_trywait + usleep的方式实现主要实现的思路:sem_trywait函数不管信号量为0或不为0都会立刻返回,当函数正常返回的时候就不usleep;当函数不正常返回时就通过usleep来实现延时,具体是实现方式如下代码中的bool Wait( size_t timeout )函数: #include <string>#include<iostream>#include<semaphore.h>#include <time.h>sem_t g_sem;// 获取自系统启动的调单递增的时间inline uint64_t GetTimeConvSeconds( timespec* curTime, uint32_t factor ){ // CLOCK_MONOTONIC:从系统启动这一刻起开始计时,不受系统时间被用户改变的影响 clock_gettime( CLOCK_MONOTONIC, curTime ); return static_cast<uint64_t>(curTime->tv_sec) * factor;}// 获取自系统启动的调单递增的时间 -- 转换单位为微秒uint64_t GetMonnotonicTime(){ timespec curTime; uint64_t result = GetTimeConvSeconds( &curTime, 1000000 ); result += static_cast<uint32_t>(curTime.tv_nsec) / 1000; return result;}// sem_trywait + usleep的方式实现// 如果信号量大于0,则减少信号量并立马返回true// 如果信号量小于0,则阻塞等待,当阻塞超时时返回falsebool Wait( size_t timeout ){ const size_t timeoutUs = timeout * 1000; // 延时时间由毫米转换为微秒 const size_t maxTimeWait = 10000; // 最大的睡眠的时间为10000微秒,也就是10毫秒 size_t timeWait = 1; // 睡眠时间,默认为1微秒 size_t delayUs = 0; // 剩余需要延时睡眠时间 const uint64_t startUs = GetMonnotonicTime(); // 循环前的开始时间,单位微秒 uint64_t elapsedUs = 0; // 过期时间,单位微秒 int ret = 0; do { // 如果信号量大于0,则减少信号量并立马返回true if( sem_trywait( &g_sem ) == 0 ) { return true; } // 系统信号则立马返回false if( errno != EAGAIN ) { return false; } // delayUs一定是大于等于0的,因为do-while的条件是elapsedUs <= timeoutUs. delayUs = timeoutUs - elapsedUs; // 睡眠时间取最小的值 timeWait = std::min( delayUs, timeWait ); // 进行睡眠 单位是微秒 ret = usleep( timeWait ); if( ret != 0 ) { return false; } // 睡眠延时时间双倍自增 timeWait *= 2; // 睡眠延时时间不能超过最大值 timeWait = std::min( timeWait, maxTimeWait ); // 计算开始时间到现在的运行时间 单位是微秒 elapsedUs = GetMonnotonicTime() - startUs; } while( elapsedUs <= timeoutUs ); // 如果当前循环的时间超过预设延时时间则退出循环 // 超时退出,则返回false return false;}// 获取需要延时等待时间的绝对时间戳inline timespec* GetAbsTime( size_t milliseconds, timespec& absTime ){ // CLOCK_REALTIME:系统实时时间,随系统实时时间改变而改变,即从UTC1970-1-1 0:0:0开始计时, // 中间时刻如果系统时间被用户改成其他,则对应的时间相应改变 clock_gettime( CLOCK_REALTIME, &absTime ); absTime.tv_sec += milliseconds / 1000; absTime.tv_nsec += (milliseconds % 1000) * 1000000; // 纳秒进位秒 if( absTime.tv_nsec >= 1000000000 ) { absTime.tv_sec += 1; absTime.tv_nsec -= 1000000000; } return &absTime;}// sem_timedwait 实现的睡眠 -- 存在缺陷// 如果信号量大于0,则减少信号量并立马返回true// 如果信号量小于0,则阻塞等待,当阻塞超时时返回falsebool SemTimedWait( size_t timeout ){ timespec absTime; // 获取需要延时等待时间的绝对时间戳 GetAbsTime( timeout, absTime ); if( sem_timedwait( &g_sem, &absTime ) != 0 ) { return false; } return true;}int main(void){ bool signaled = false; uint64_t startUs = 0; uint64_t elapsedUs = 0; // 初始化信号量,数量为0 sem_init( &g_sem, 0, 0 ); ////////////////////// sem_trywait+usleep 实现的睡眠 //////////////////// // 获取开始的时间,单位是微秒 startUs = GetMonnotonicTime(); // 延时等待 signaled = Wait(1000); // 获取超时等待的时间,单位是微秒 elapsedUs = GetMonnotonicTime() - startUs; // 输出 signaled:0 Wait time:1000ms std::cout << "signaled:" << signaled << "\t Wait time:" << elapsedUs/1000 << "ms" << std::endl; ////////////////////// sem_timedwait 实现的睡眠 //////////////////// ///////////////////// 存在缺陷,原因当在sem_timedwait阻塞中时,修改了系统时间,则会导致sem_timedwait一直阻塞 ////////////////// // 获取开始的时间,单位是微秒 startUs = GetMonnotonicTime(); // 延时等待 signaled = SemTimedWait(2000); // 获取超时等待的时间,单位是微秒 elapsedUs = GetMonnotonicTime() - startUs; // 输出 signaled:0 SemTimedWait time:2000ms std::cout << "signaled:" << signaled << "\t SemTimedWait time:" << elapsedUs/1000 << "ms" << std::endl; return 0;}测试结果: ...

August 18, 2019 · 3 min · jiezi

欢迎加入我的知识星球C语言解惑课堂

我在知识星球上开通了一个有关C语言基础答疑解惑的星球,它叫做:“C语言解惑课堂”。看这名字你就知道虽然有点俗,俗才贴近你的真正需求嘛!这是一个专门帮助C语言初学者答疑解惑的课堂。嗯~~~,关于这个星球我是有一些想法的,各位看官请继续看! 一、我为啥要开通这个星球? 我在知乎、今日头条、CSDN、微信公众号等平台做C语言基础答疑工作差不多有三四年时间了,这期间我有跟不少同学交流,在为同学们答疑解惑的过程中我有以下几点感触,不知你们是否感同身受。 1、C语言基础的疑难点太多了 很多同学学习C程序课程差不多是大二学年,早一点的大一学年学的也有,其中不少同学是非计算机专业的。在学习这门课程前,同学们的计算机基础也是各不相同,计算机基本功好一点的学习C语言时也许感觉还好一点,基本功薄弱一点的同学可能就比较吃力了,实际上在学习C语言之前大部分人的计算机基础都是薄弱的,于是在学习过程中就会感觉力不从心,从而都有一句口头禅:“这是啥?这TM又是啥?” 同学们的问题其实都很基础,之所以不会还是基本知识没能理解,话说这反而大大激发了我“帮助的热情”!得,索性我找个地方把大家都聚集起来,一起学习讨论C语言基础,在知识星球上开通一个圈子就是个不错的选择。 2、C语言的课时有点短 C程序课程一般要求一学期修完,而一学期的课时你掐掐手指都能数过来,真的很少。在这么短的时间内完完整整学完一本C程序教材是可以的,但是要完完全全地理解C语言基础就不太可能了,而且老师也不太可能在这么短的课时内把C语言所有的基础知识点全部讲到,这就导致了同学们对不少知识点没有学懂、没有学深,不知如何写程序。 你可以回想,你感觉好像C语言学会了,可是当你坐在电脑前编写C程序,是不是觉得脑袋周围一圈的问号?在我的星球里,所有的C语言基础疑难知识点解惑都可以沉淀,不管你什么时候加入我的星球,都可以看到往期的内容,不受时间的限制。 3、C语言基础有些知识点确实有点绕脑筋的 C语言是一门编程语言,它的抽象性较高。抽象的东西本身就不是特别容易理解的,因此有一些知识点是颇有点绕脑筋的意味,比如指针、二维数组、链表等。这就像顺口溜让你快速念出来,没有经过专门训练的话很难做到吧! 于是我在想怎么来给同学们解答这些知识点呢?有那么一天我手捧泡面突然灵光乍现,何不来点“饭后甜点”?同学们学习归学习,那么我就来整理C程序课程的疑难杂点,把这些点一条条地梳理出来,一条条地分门别类的归纳好,并且一条条地形象地、深入地、细致地讲解,在拉家常式的讲法中彻底理解这些疑难点,你学完了就可以来我这看看了。 换句话说,我做的事不是从头到尾把教程再写一遍,而是更加像是编写“C语言答疑解惑大全”! 4、C语言解惑内容可以沉淀 我在其他平台写文章,随着文章数越来越多,同学们想查看某个文章时可能找不到很好的查询办法,而且我自己也不一定记得我写过什么文章,所以找起来确实不方便。 在我的星球中,我写过的所有内容都沉淀下来,同学们加入后可以看到往期我写的所有内容,如果要查询也是很方便的。星球提供了搜索入口,同学们也可以根据自己的需求做关键字搜索,体验也十分不错。 二、我的星球的目标 做任何事情都要有一个目标,我做这个星球也不例外。我想了很长时间,我觉得我的星球“C语言解惑课堂”的目标是为C语言初学者解答C语言基础中的所有疑难点,打好基础,为以后的C语言进阶、学习其他技术打好基础,省的你遇到不懂的问题抱着书问你问他的,有时还找不到人问,而在这里也许你就找到了。 用一句话来概括,应该就是“让天下没有盘不会的C语言基础疑难点”! 三、我的星球提供哪些内容? 我在这个星球中致力于主动输出高价值的C语言基础疑难点解惑内容,而且都沉淀在这里,内容如下: 1、【C语言学习书籍推荐】 同学们问的比较多的一个问题是“有没有好的C语言学习书籍?”你们呀,也别烦了!我索性为你整理一份经典的计算机基础书单、C语言基础书单和C语言进阶书单等,你就直接来看就行了。 这是星球里一张书单的截图: 书单中的书都是京东的,你看中了哪本书,直接在书单里一站式购买,方便快捷! 2、【C语言编程软件推荐】 同学们也会经常问的一个问题:“有哪些好的C语言编程环境?”我也为你整理了大家伙常用的几款C语言编程软件,并尽量贴出官网下载地址,你也是直接来看就行。 3、【C语言学习的正确姿势】 同学们问的最多的估计就是这个问题了:“我怎么学习C语言呢?” 学习C语言不仅是一项智商活动,也是一项情商活动,我也给你准备了一些学习建议与方法。 这是星球里一张关于学习C语言方法的截图: 只要你真有心学习C语言,你一定能学成! 4、【C语言基础疑难点剖析】 这是我的星球主要做的事情之一。我为你整理、剖析C语言基础的所有疑难点,帮你把这些点各个击破,并且归类好,建立好索引,方便查询,助你C语言基础相关的考试、升学、工作一臂之力。还有盘不会的疑难点?我不信! 这是星球里一张疑难点剖析的截图: 5、【C语言基础试题剖析】 这也是我的星球主要做的事情之一。我为你整理、剖析C语言基础中的难题与教程的习题,帮你击破难题中的难点。 这是星球里一张试题剖析的截图: 6、【C程序代码参考】 这也是我的星球主要做的事情之一。您在学习C语言中,经常要做C编程题吧,您是不是一头雾水,无从下手?这编程题可都是考试、笔试特爱考的题型,而且往往是大题,分值比较高。我会经常给出一些编程题,并给出代码实现供参考。 这是星球里一张代码实现的截图: 7、【C语言基础学习资料】 这也是我的星球主要做的事情之一。我会与你分享优质的学习资料,让你获取高价值的学习资源。 这是星球里一张学习资料的截图: 8、【你问我答】 另外,同学们在学习C语言基础的过程中有疑问也可在星球里提出来,可以是教程上的某道题某段话等,我们一起讨论,多多互动,多多交流。 一个人的C语言学习之路是枯燥的,一群人在一起学习交流是才有趣的、有动力的! 还有更多内容,后续会推出! 四、我的星球内容的展现形式是怎样的? 星球内的内容展现形式必须得采用形象直观的,譬如全部采用如下基本三级结构形式: 【第XX篇】【大类】【小类】 1、【第XX篇】:第一个方括号表明当前内容是星球里的第几篇内容。 2、【大类】:第二个方括号表明当前内容所属的大类,比如“基础”类、“试题”类、“代码”类、“资料”类等。 3、【小类】:第三个方括号表明当前内容的提炼,比如“int类型的取值范围是多少?”、“用C实现简单的文本复制程序”等。 如果问题比较复杂,可能还会有第四个方括号。 采用这种三级结构形式,也是方便同学们的搜索。如果您记得篇号,那么可以直接搜索篇号,如下图: 如果您记得标题关键词,那么可以搜索关键词,如下图: 超简单有木有! 五、这星球适合哪些人? 如果你属于下述的几种情况之一,那么我的星球很适合你,欢迎你的加入! 1、刚入门C语言的 2、正在学习C语言打基础的,遇到很多疑问的找不到解答的 3、C语言看似学了一遍还是云里雾里的 4、学完C语言还是不懂的,不会做题的 5、有兴趣自学C语言的 六、加入的费用 ...

August 18, 2019 · 1 min · jiezi

跟我一起学习pybind11-之一

关于pybind11pybind11是一个轻量级的“Header-only”的库,它将C++的类型暴露给Python,反之亦然。主要用于将已经存在的C++代码绑定到Python。pybind11的目标和语法都类似于boost.python库。利用编译时的内省来推断类型信息。 boost.python最大问题在于,boost太过复杂和庞大。pybind11除去注释,代码仅仅4000多行,需要依赖Python2.7或Python3。 支持的编译器Clang/LLVM (any non-ancient version with C++11 support)GCC 4.8 or newerMicrosoft Visual Studio 2015 or newerIntel C++ compiler v17 or newer开始使用pybind11介绍pybind11的基本特性。 编译测试用例Linux/MacOS需要安装python-dev或者python3-dev、cmake。 mkdir buildcd buildcmake ..make check -j 4最后一行命令make check -j 4将会编译并自动执行测试用例。 Windows仅仅支持Visual Studio 2015以及更新的版本。 mkdir buildcd buildcmake ..cmake --build . --config Release --target check以上命令将会创建一个Visual Studio工程,并且该工程将会被自动编译。 注意:如果所有的测试都失败了,请确保Python二进制类型和测试用例被编译的二进制类型与处理器类型匹配。 头文件和命名空间为了简洁起见,所有的示例都将假设存在以下两行代码: #include <pybind11/pybind11.h>namespace py = pybind11;某些功能也许需要其它更多的头文件,但是那些部分将在需要时列出来。 绑定简单函数让我们以一个极度简单的函数来开始创建python绑定,函数完成两数相加并返回结果 int add(int i, int j){ return i + j;}为简单起见,我们将函数和绑定代码都放在example.cpp这个文件中 #include <pybind11/pybind11.h>namespace py = pybind11;int add(int i, int j){ return i + j;}PYBIND11_MODULE(example, m){ m.doc() = "pybind11 example plugin"; // 可选的模块说明 m.def("add", &add, "A function which adds two numbers");}PYBIND11_MODULE()宏函数将会创建一个函数,在由Python发起import语句时该函数将会被调用。模块名字“example”,由宏的第一个参数指定(千万不能出现引号)。第二个参数"m",定义了一个py::module的变量。函数py::module::def()生成绑定代码,将add()函数暴露给Python。 ...

August 18, 2019 · 2 min · jiezi

Redis-Module原理一

Redis自4.0版本之后便支持了模块扩展功能, 在使用的过程中发现了一些Redis实现的疑问, Redis推荐开发人员通过引入redismodule.h, 来调用指定接口来支持扩展, 其中要求实现程序必须实现RedisModule_OnLoad方法, 该方法主要加载模块, 注册相应的api, 对context上下文注入, 那么它是什么时候被调用的呢? 又比如Redis为开发人员提供几组API, 比如RedisModule_StringPtrlen用于返回抽象类型字符串的长度, 但是翻遍所有的代码也没有该函数声明的具体实现, 那么它是什么时候在哪里被实现的呢? 带着这些问题这篇文章会做出解答 (一) 使用 首先根据 官方文档 , 用户扩展模块需要通过配置文件指定需要加载的链接库 *通过配置loadmodule指定用户扩展的链接库* 当然动态库需要用户自己去编译生成, 在编译之前需要将指定的module引入redismodule.h头文件, 最简单的方式是直接将源文件拉入modules下, 修改MakeFile文件并执行make编译 *修改MakeFile* 那么redis是何时开始加载的呢? (二)链接库的加载 redis服务端的入口在server.c源文件中, 其主要任务为初始化数据结构, 初始化设置, 启动eventloop事件加载器等等, 其中在启动事件加载器之前, redis会先加载指定的链接库(redis也支持通过命令行加载库) server.sentinel_mode = checkForSentinelMode(argc,argv); initServerConfig(); ACLInit(); /* The ACL subsystem must be initialized ASAP because the basic networking code and client creation depends on it. */ //初始化模块 加载动态链接库 moduleInitModulesSystem(); moduleInitModulesSystem函数负责加载模块 ...

August 17, 2019 · 2 min · jiezi

C程序员专用表白程序让你度过一个美妙的七夕节

今天的别人,今天的你 [代码运行效果截图] include<iostream.h>include<windows.h>include<stdio.h>define stoptimeshort 40define stoptimelong 100void main() { ////////////////// char ch[10]; int f9={ 0,1,1,1,1,1,0,0,0,0,0,0,1,1,0,0,0,0,0,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1, 0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,1,0,1,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,1, 0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,1, 0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,1, 0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,1, 0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,1, 0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1, 0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, 0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0 }; int s9={ 0,0,0,1,1,0,0,0,0,0,1,1,0,0,0,0,1,1,0,0,0, 0,1,0,0,0,0,1,0,1,0,0,0,0,1,0,0,0,0,0,1,0, 1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1, 0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0, 0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0, 0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0, 0,0,0,0,0,0,1,0,1,0,0,0,1,0,1,0,0,0,0,0,0, 0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0, }; int t9={ 0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0, 0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0, 1,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1, 0,1,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1,0, 0,0,1,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,1,0,0, 0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,1,0,0,0,0, 0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,1,1,0,0,0,0,0, 0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0, }; system ( "title I Love You" ); ////////////////// printf ( "%s"," " ); Sleep ( stoptimelong ); ...

August 8, 2019 · 5 min · jiezi

socket的SOREUSEADDR参数全面分析

在具体分析之前,我们先看下socket(7)的man文档对这个参数是怎么介绍的: SO_REUSEADDR Indicates that the rules used in validating addresses supplied in a bind(2) call should allow reuse of local addresses. For AF_INET sockets this means that a socket may bind, except when there is an active listening socket bound to the address. When the listening socket is bound to INADDR_ANY with a spe‐ cific port then it is not possible to bind to this port for any local address. Argument is an integer boolean flag.从这段文档中我们可以知道三个事: ...

August 7, 2019 · 5 min · jiezi

C语言建立链表并实现增删查改

以下是本人完成的一个C语言建立链表并进行增删查改操作的程序,为方便学习,本人将整个程序分为头文件和主函数两部分: 1.头文件(函数部分)(1)初始化函数 #include <stdio.h>#include <stdlib.h>typedef struct { int *head; int length; int capacity;} Toslist; //Toslist类型//初始化顺序表Toslist initSeqlist() { Toslist list; list.length = 0; list.capacity = 5; list.head = (int *)malloc(10 * sizeof(int)); if (!list.head) { printf("初始化失败!\n"); exit(0); } return list;}(2)打印函数 //打印顺序表void displayList(Toslist list) { for (int i = 0; i < list.length; i++) { printf("%d ", list.head[i]); } printf("\n");}(3)插入函数 //插入元素Toslist add(Toslist list, int elem, int pos) { if (list.length == list.capacity) { int *temp = (int *)realloc(list.head, (list.capacity + 1) * sizeof(int));//判断空间是否足够,不够就另建链表//不直接用head而引入temp的作用:防止空间分配失败导致head失去原来的链表 if (!temp) { list.head = temp; list.capacity += 1; } } //插入位置及以后的元素后移 for (int i = list.length - 1; i >= pos; i--) { list.head[i + 1] = list.head[i]; } list.head[pos] = elem; list.length ++; return list; if (pos > list.length || pos < 0) printf("插入位置错误!\n"); return list;}(4)删除函数 ...

August 7, 2019 · 2 min · jiezi

FRR学习第九天完整的数据中心网络模型

网络拓扑 拓扑说明实验环境是一台16G内存的主机。上面使用vmware运行了三个虚拟机,运行的系统为ubuntu-19.04。三个虚拟机采用host-only模式连接。 spine,leaf1,leaf2三个设备均为ubuntu-19.04.上面运行了FRR程序。host1,host2,host3,host4为网络命名空间。underlay网络采用的是二层模式(局限于实验条件)整个实验是一个比例缩小的数据中心spine-leaf模型。leaf2还需要作为边界网关,使用默认路由将流量发送到公网,同时作为firewall(这里只进行了nat)。 leaf作为border和vtep的功能细化图 实验功能说明整个数据中心一个租户,使用vni:100作为租户的l3vni租户使用了三个subnet。1.1.1.0/24子网有两个虚机,分布在两个vtep下,使用10作为l2vni。2.2.2.0/24和3.3.3.0/24都只有一个虚机。5.5.5.0/24作为relay子网用于连接default-vrf和evpn-vrf。整个实验需要实现租户内所有主机互通,同时主机可以访问公网。(暂时不能实现公网访问虚机,需要申请floating-ip才可以,申请了公网IP后,可以在default-vrf中做1:1 nat实现互访)spine配置bgp evpn配置router bgp 7677 bgp router-id 192.168.59.130 bgp bestpath as-path multipath-relax neighbor fabric peer-group neighbor fabric remote-as external neighbor 192.168.59.128 peer-group fabric neighbor 192.168.59.129 peer-group fabric ! address-family l2vpn evpn neighbor fabric activate exit-address-family!leaf1配置接口配置#开启转发sudo sysctl -w net.ipv4.ip_forward=1 sudo sysctl -p#添加host1sudo ip netns add host1sudo ip link add veth1 type veth peer name eth0 netns host1sudo ip netns exec host1 ip link set lo upsudo ip netns exec host1 ip link set eth0 upsudo ip netns exec host1 ip addr add 1.1.1.1/24 dev eth0sudo ip netns exec host1 ip route add default via 1.1.1.254 dev eth0sudo ip link add br10 type bridgesudo ip link add vxlan10 type vxlan id 10 local 192.168.59.128 dstport 4789 nolearningsudo ip link set br10 upsudo ip link set veth1 upsudo ip link set vxlan10 upsudo ip link set veth1 master br10sudo ip link set vxlan10 master br10sudo ip addr add 1.1.1.254/24 dev br10sudo ip link set dev br10 address 00:00:01:02:03:10 #分布式二层网关,mac需要一致#添加host2sudo ip netns add host2sudo ip link add veth2 type veth peer name eth0 netns host2sudo ip netns exec host2 ip link set lo upsudo ip netns exec host2 ip link set eth0 upsudo ip netns exec host2 ip addr add 2.2.2.2/24 dev eth0sudo ip netns exec host2 ip route add default via 2.2.2.254 dev eth0sudo ip link add br20 type bridgesudo ip link set br20 upsudo ip link set veth2 upsudo ip link set veth2 master br20sudo ip addr add 2.2.2.254/24 dev br20#添加vni 100,作为l3vnisudo ip link add br100 type bridgesudo ip link add vxlan100 type vxlan id 100 local 192.168.59.128 dstport 4789 nolearningsudo ip link set br100 upsudo ip link set vxlan100 upsudo ip link set vxlan100 master br100 #sudo ip addr add 5.5.5.254/24 dev br100 切记,作为l3vni的svi接口不能配置IP,否则收到type-5路由不会安装。sudo ip link set dev br100 address 00:00:01:02:03:04 #这个是路由mac#添加vrfsudo ip link add evpn-vrf type vrf table 100sudo ip link set evpn-vrf upsudo ip link set br100 master evpn-vrf sudo ip link set br10 master evpn-vrf sudo ip link set br20 master evpn-vrf bgp evpn配置vrf evpn-vrf vni 100 exit-vrf!router bgp 7675 bgp router-id 192.168.59.128 bgp bestpath as-path multipath-relax neighbor fabric peer-group neighbor fabric remote-as external neighbor 192.168.59.130 peer-group fabric ! address-family l2vpn evpn neighbor fabric activate advertise-all-vni exit-address-family!router bgp 7675 vrf evpn-vrf ! address-family ipv4 unicast network 2.2.2.0/24 exit-address-family ! address-family l2vpn evpn advertise ipv4 unicast exit-address-family!注: ...

August 7, 2019 · 5 min · jiezi

fopen函数找不到文件

有同学问我,以下代码会输出“===”,为什么呀? if( (fp = fopen("data.dat","r"))==NULL){printf("===");} 我看了下,代码是以“读”的方式打开data.dat文件,可是代码都没有指定data.dat 在哪里,程序找不到这个文件,所以就认为出错了。咋办呢?要么创建data.dat文件,且指定文件的路径;要么就以“写”方式打开文件,则data.dat文件不存,程序也会新建一个data.dat文件。

July 16, 2019 · 1 min · jiezi

C语言的int-main

一个c程序(除特殊文件)不可缺少的就是int main(){ }当然了,main 也可以换成其他它名字。在大括号中,便可以写出要干的代码了。 并可多写几个不同名字。

July 16, 2019 · 1 min · jiezi

哈希映射有效的字母异位词

南朝四百八十寺多少楼台烟雨中 前言本题摘自LeetCode第242题,有效的字母异位词, 题目描述给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。 事例1: 输入: s = "anagram", t = "nagaram"输出: true事例2: 输入: s = "rat", t = "car"输出: false解题思想1.初始化两个数组(理解为哈希表),对应26个英文字母,开始值都为0 int a[26] = {0}; int b[26] = {0}; 2.分别遍历s、t字符串的中的字符,给字符在数组中的位置值进行加加操作 3.对比a、b数组中的值是否一致 C语言代码实现 BOOL isVaildAgment(char *s, char *t) { int lengthS = strlen(s); int lengthT = strlen(t); int a[26] = {0}; int b[26] = {0}; for (NSInteger i = 0; i < lengthS; i++) { int index = s[i]-'a'; a[index] = 1; } for (NSInteger i = 0; i < lengthT; i++) { int index = t[i]-'a'; b[index] = 1; } for (NSInteger i = 0; i < 26; i++) { if (a[i] != b[i]) { return false; } } return true; }

July 16, 2019 · 1 min · jiezi

C语言随笔区分与

写C程序时,经常发现大家=与==分不清。最常见的写法如下:int a = 3;if(a = 1){.......} 写程序的人原意是想如果a等于1的话,就执行花括号里的语句,a初始化时的值是3,也就是不会执行,但实际呢?却执行了花括号里的语句。 这个也不能怪C程序的初学者,大家学了那么多年的数序,=是等于号早就深入人心,现在C语言告诉你==才是等于号,放谁也一时半会儿接收不了,难道就真没有一个方法能避免此类错误吗?当然不是。我教大家一个联想的办法。数学中,=是等于号,那么在心中告诉自己,“数等”就是数学中等于号的意思,且“数”与“傻”发音相似,那么就记住“傻等”,在写关于相等的程序代码时,不断的念傻等傻等,迫使自己联想到数等,数学中的等于号,如果自己写成数学中的等于号,也就是一个“=”,那自己就是“傻”,所以要写成“==”。 希望这个联想法能帮助大家。

July 16, 2019 · 1 min · jiezi

PHP源码学习20190409-FastCGI协议1

baiyan 全部视频:https://segmentfault.com/a/11... 计算机网络架构的分层与封装我们经常谈到,计算机网络有多种体系架构,如OSI七层模型、TCP/IP四层模型等等。那么,为什么要将计算机网络分成这么多层呢?而且,每一层都要加上其特有的头部(如TCP头部、IP头部等等)进行封装,这样设计的原因又何在?首先我们看一个我们经常看到的C/S架构数据流动过程图: 我们以TCP/IP四层网络体系架构为例,总结一下各层服务的作用:应用层:用户直接能够操作的层,处理应用程序的逻辑,在用户空间实现传输层:为两台计算机上的应用程序提供端到端的通信,实现了数据经过网络层到达计算机后能够分发到各个端口(分用),在内核空间实现网络层:提供精确到两端计算机互联网IP地址的通信,实现了数据包在互联网上的选路与转发,在内核空间实现数据链路层:提供基于物理媒介(如网卡)的数据传输服务,在内核空间实现首先谈谈为什么要分层。在我们实际的项目开发过程中,我们常常提到的模块化、服务化,其实和计算机网络为什么要分层的原理类似。我们可以将每一层都看成一个模块、一个服务,其内部的实现细节对于上一层或者下一层都是隐藏的,可能只对外暴露一些API而已。如果上层要使用下层提供的服务,根本不需要关注下一层内部的实现细节,只需使用下层提供的API即可。这样一来,方便了开发与维护,进而提高了计算机网络整体的运作效率。我们再谈为什么要封装。刚才我们谈到了每个模块都是独立的。上层为了使用下层提供的服务,必须封装。举个例子,应用层想使用传输层提供的端到端的数据传输服务,那么传输层就会将应用层下发的数据先存起来。为了实现端到端的数据传输,传输层模块内部需要标识双方的端口号。所以,它就会将端口号还有一些额外的信息(被称作(TCP/UDP)首部),和应用层下发的数据部分进行封装,一起传给下一层(即网络层)。这样,通过封装每一层特有的头部,实现了每一层独特的功能,最终实现了精确到端口号的端到端数据传输服务。为什么需要FastCGI协议在LNMP架构中,客户端浏览器与nginx代理是通过HTTP协议进行通信的。在请求到达nginx之后,数据最终会被转发给上游的PHP-FPM。它们之间的通信,属于同一机器上、不同端口号之间的通信。所以,它们也需要一个类似于HTTP的协议的、一种格式或语法上的规范与约定。这样一来,通信双方才能够更好地理解并解析对方传输的信息以及数据。这个协议就是FastCGI协议。它可以基于传输层TCP协议来实现。抓包接下来我们会以nginx与PHP-FPM之前通信的数据包为例,利用tcpdump抓取二者通信的数据包。所以我们首先需要了解一下各层数据报头部的格式,但是具体每个字段的作用,我们会在接下来详细讲解,先让我们熟悉一下:数据链路层(以太网帧为例) 网络层(IPv4协议为例) 传输层(TCP协议为例) 各层之间数据包的联系数据链路层是对网络层数据及头部的封装;网路层是对传输层数据及头部的封装;传输层是对应用层数据及头部的封装: 由于FastCGI协议基于TCP协议,那么它一定会进行三次握手与四次挥手,中间的数据包是双方的数据交换,全部的数据包如下:[root@VM_0_3_centos ~]# tcpdump -i lo port 9000 -S -XXtcpdump: verbose output suppressed, use -v or -vv for full protocol decodelistening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes13:50:43.883490 IP VM_0_3_centos.33844 > VM_0_3_centos.cslistener: Flags [S], seq 608546013, win 43690, options [mss 65495,sackOK,TS val 961901286 ecr 0,nop,wscale 7], length 0 0x0000: 0000 0000 0000 0000 0000 0000 0800 4500 ..............E. 0x0010: 003c de3b 4000 4006 5e7e 7f00 0001 7f00 .<.;@.@.^~...... 0x0020: 0001 8434 2328 2445 acdd 0000 0000 a002 ...4#($E........ 0x0030: aaaa fe30 0000 0204 ffd7 0402 080a 3955 ...0..........9U 0x0040: 72e6 0000 0000 0103 0307 r.........13:50:43.883520 IP VM_0_3_centos.cslistener > VM_0_3_centos.33844: Flags [S.], seq 2973795481, ack 608546014, win 43690, options [mss 65495,sackOK,TS val 961901286 ecr 961901286,nop,wscale 7], length 0 0x0000: 0000 0000 0000 0000 0000 0000 0800 4500 ..............E. 0x0010: 003c 0000 4000 4006 3cba 7f00 0001 7f00 .<..@.@.<....... 0x0020: 0001 2328 8434 b140 8499 2445 acde a012 ..#(.4.@..$E.... 0x0030: aaaa fe30 0000 0204 ffd7 0402 080a 3955 ...0..........9U 0x0040: 72e6 3955 72e6 0103 0307 r.9Ur.....13:50:43.883541 IP VM_0_3_centos.33844 > VM_0_3_centos.cslistener: Flags [.], ack 2973795482, win 342, options [nop,nop,TS val 961901286 ecr 961901286], length 0 0x0000: 0000 0000 0000 0000 0000 0000 0800 4500 ..............E. 0x0010: 0034 de3c 4000 4006 5e85 7f00 0001 7f00 .4.<@.@.^....... 0x0020: 0001 8434 2328 2445 acde b140 849a 8010 ...4#($E...@.... 0x0030: 0156 fe28 0000 0101 080a 3955 72e6 3955 .V.(......9Ur.9U 0x0040: 72e6 r.13:50:43.883594 IP VM_0_3_centos.33844 > VM_0_3_centos.cslistener: Flags [P.], seq 608546014:608546982, ack 2973795482, win 342, options [nop,nop,TS val 961901286 ecr 961901286], length 968 0x0000: 0000 0000 0000 0000 0000 0000 0800 4500 ..............E. 0x0010: 03fc de3d 4000 4006 5abc 7f00 0001 7f00 ...=@.@.Z....... 0x0020: 0001 8434 2328 2445 acde b140 849a 8018 ...4#($E...@.... 0x0030: 0156 01f1 0000 0101 080a 3955 72e6 3955 .V........9Ur.9U 0x0040: 72e6 0101 0001 0008 0000 0001 0000 0000 r............... 0x0050: 0000 0104 0001 03a0 0000 0f35 5343 5249 ...........5SCRI 0x0060: 5054 5f46 494c 454e 414d 452f 6461 7461 PT_FILENAME/data 0x0070: 2f77 7777 2f68 7464 6f63 732f 6461 7461 /www/htdocs/data 0x0080: 2f77 7777 2f68 7464 6f63 732f 736e 6f2f /www/htdocs/sno/ 0x0090: 7075 626c 6963 2f69 6e64 6578 2e70 6870 public/index.php 0x00a0: 0c00 5155 4552 595f 5354 5249 4e47 0e03 ..QUERY_STRING.. 0x00b0: 5245 5155 4553 545f 4d45 5448 4f44 4745 REQUEST_METHODGE 0x00c0: 540c 0043 4f4e 5445 4e54 5f54 5950 450e T..CONTENT_TYPE. 0x00d0: 0043 4f4e 5445 4e54 5f4c 454e 4754 480b .CONTENT_LENGTH. 0x00e0: 0a53 4352 4950 545f 4e41 4d45 2f69 6e64 .SCRIPT_NAME/ind 0x00f0: 6578 2e70 6870 0b01 5245 5155 4553 545f ex.php..REQUEST_ 0x0100: 5552 492f 0c01 444f 4355 4d45 4e54 5f55 URI/..DOCUMENT_U 0x0110: 5249 2f0d 2b44 4f43 554d 454e 545f 524f RI/.+DOCUMENT_RO 0x0120: 4f54 2f64 6174 612f 7777 772f 6874 646f OT/data/www/htdo 0x0130: 6373 2f64 6174 612f 7777 772f 6874 646f cs/data/www/htdo 0x0140: 6373 2f73 6e6f 2f70 7562 6c69 630f 0853 cs/sno/public..S 0x0150: 4552 5645 525f 5052 4f54 4f43 4f4c 4854 ERVER_PROTOCOLHT 0x0160: 5450 2f31 2e31 0e04 5245 5155 4553 545f TP/1.1..REQUEST_ 0x0170: 5343 4845 4d45 6874 7470 1107 4741 5445 SCHEMEhttp..GATE 0x0180: 5741 595f 494e 5445 5246 4143 4543 4749 WAY_INTERFACECGI 0x0190: 2f31 2e31 0f0c 5345 5256 4552 5f53 4f46 /1.1..SERVER_SOF 0x01a0: 5457 4152 456e 6769 6e78 2f31 2e31 312e TWAREnginx/1.11. 0x01b0: 390b 0f52 454d 4f54 455f 4144 4452 3131 9..REMOTE_ADDR11 0x01c0: 332e 3232 372e 3234 392e 3132 370b 0552 3.227.249.127..R 0x01d0: 454d 4f54 455f 504f 5254 3533 3931 330b EMOTE_PORT53913. 0x01e0: 0a53 4552 5645 525f 4144 4452 3137 322e .SERVER_ADDR172. 0x01f0: 3136 2e30 2e33 0b02 5345 5256 4552 5f50 16.0.3..SERVER_P 0x0200: 4f52 5438 300b 0d53 4552 5645 525f 4e41 ORT80..SERVER_NA 0x0210: 4d45 6772 6170 652e 7961 662e 636f 6d0f MEgrape.yaf.com. 0x0220: 0352 4544 4952 4543 545f 5354 4154 5553 .REDIRECT_STATUS 0x0230: 3230 3009 0f48 5454 505f 484f 5354 3132 200..HTTP_HOST12 0x0240: 322e 3135 322e 3232 392e 3232 310f 0a48 2.152.229.221..H 0x0250: 5454 505f 434f 4e4e 4543 5449 4f4e 6b65 TTP_CONNECTIONke 0x0260: 6570 2d61 6c69 7665 1209 4854 5450 5f43 ep-alive..HTTP_C 0x0270: 4143 4845 5f43 4f4e 5452 4f4c 6d61 782d ACHE_CONTROLmax- 0x0280: 6167 653d 301e 0148 5454 505f 5550 4752 age=0..HTTP_UPGR 0x0290: 4144 455f 494e 5345 4355 5245 5f52 4551 ADE_INSECURE_REQ 0x02a0: 5545 5354 5331 0f79 4854 5450 5f55 5345 UESTS1.yHTTP_USE 0x02b0: 525f 4147 454e 544d 6f7a 696c 6c61 2f35 R_AGENTMozilla/5 0x02c0: 2e30 2028 4d61 6369 6e74 6f73 683b 2049 .0.(Macintosh;.I 0x02d0: 6e74 656c 204d 6163 204f 5320 5820 3130 ntel.Mac.OS.X.10 0x02e0: 5f31 355f 3029 2041 7070 6c65 5765 624b _15_0).AppleWebK 0x02f0: 6974 2f35 3337 2e33 3620 284b 4854 4d4c it/537.36.(KHTML 0x0300: 2c20 6c69 6b65 2047 6563 6b6f 2920 4368 ,.like.Gecko).Ch 0x0310: 726f 6d65 2f37 352e 302e 3337 3730 2e31 rome/75.0.3770.1 0x0320: 3030 2053 6166 6172 692f 3533 372e 3336 00.Safari/537.36 0x0330: 0b76 4854 5450 5f41 4343 4550 5474 6578 .vHTTP_ACCEPTtex 0x0340: 742f 6874 6d6c 2c61 7070 6c69 6361 7469 t/html,applicati 0x0350: 6f6e 2f78 6874 6d6c 2b78 6d6c 2c61 7070 on/xhtml+xml,app 0x0360: 6c69 6361 7469 6f6e 2f78 6d6c 3b71 3d30 lication/xml;q=0 0x0370: 2e39 2c69 6d61 6765 2f77 6562 702c 696d .9,image/webp,im 0x0380: 6167 652f 6170 6e67 2c2a 2f2a 3b71 3d30 age/apng,*/*;q=0 0x0390: 2e38 2c61 7070 6c69 6361 7469 6f6e 2f73 .8,application/s 0x03a0: 6967 6e65 642d 6578 6368 616e 6765 3b76 igned-exchange;v 0x03b0: 3d62 3314 0d48 5454 505f 4143 4345 5054 =b3..HTTP_ACCEPT 0x03c0: 5f45 4e43 4f44 494e 4767 7a69 702c 2064 _ENCODINGgzip,.d 0x03d0: 6566 6c61 7465 140e 4854 5450 5f41 4343 eflate..HTTP_ACC 0x03e0: 4550 545f 4c41 4e47 5541 4745 7a68 2d43 EPT_LANGUAGEzh-C 0x03f0: 4e2c 7a68 3b71 3d30 2e39 0104 0001 0000 N,zh;q=0.9...... 0x0400: 0000 0105 0001 0000 0000 ..........13:50:43.883602 IP VM_0_3_centos.cslistener > VM_0_3_centos.33844: Flags [.], ack 608546982, win 357, options [nop,nop,TS val 961901286 ecr 961901286], length 0 0x0000: 0000 0000 0000 0000 0000 0000 0800 4500 ..............E. 0x0010: 0034 f67c 4000 4006 4645 7f00 0001 7f00 .4.|@.@.FE...... 0x0020: 0001 2328 8434 b140 849a 2445 b0a6 8010 ..#(.4.@..$E.... 0x0030: 0165 fe28 0000 0101 080a 3955 72e6 3955 .e.(......9Ur.9U 0x0040: 72e6 r.13:50:43.885366 IP VM_0_3_centos.cslistener > VM_0_3_centos.33844: Flags [P.], seq 2973795482:2973795650, ack 608546982, win 357, options [nop,nop,TS val 961901288 ecr 961901286], length 168 0x0000: 0000 0000 0000 0000 0000 0000 0800 4500 ..............E. 0x0010: 00dc f67d 4000 4006 459c 7f00 0001 7f00 ...}@.@.E....... 0x0020: 0001 2328 8434 b140 849a 2445 b0a6 8018 ..#(.4.@..$E.... 0x0030: 0165 fed0 0000 0101 080a 3955 72e8 3955 .e........9Ur.9U 0x0040: 72e6 0106 0001 008d 0300 582d 506f 7765 r.........X-Powe 0x0050: 7265 642d 4279 3a20 5048 502f 372e 322e red-By:.PHP/7.2. 0x0060: 3132 0d0a 436f 6e74 656e 742d 5479 7065 12..Content-Type 0x0070: 3a61 7070 6c69 6361 7469 6f6e 2f6a 736f :application/jso 0x0080: 6e3b 2063 6861 7273 6574 3d75 7466 2d38 n;.charset=utf-8 0x0090: 0d0a 0d0a 7b22 7374 6174 7573 223a 3531 ....{"status":51 0x00a0: 342c 226d 7367 223a 2255 6e65 7863 6570 4,"msg":"Unexcep 0x00b0: 7465 6420 6120 656d 7074 7920 636f 6e74 ted.a.empty.cont 0x00c0: 726f 6c6c 6572 206e 616d 6522 2c22 6461 roller.name","da 0x00d0: 7461 223a 5b5d 7d00 0000 0103 0001 0008 ta":[]}......... 0x00e0: 0000 0000 0000 0077 5c2f .......w\/13:50:43.885378 IP VM_0_3_centos.33844 > VM_0_3_centos.cslistener: Flags [.], ack 2973795650, win 350, options [nop,nop,TS val 961901288 ecr 961901288], length 0 0x0000: 0000 0000 0000 0000 0000 0000 0800 4500 ..............E. 0x0010: 0034 de3e 4000 4006 5e83 7f00 0001 7f00 .4.>@.@.^....... 0x0020: 0001 8434 2328 2445 b0a6 b140 8542 8010 ...4#($E...@.B.. 0x0030: 015e fe28 0000 0101 080a 3955 72e8 3955 .^.(......9Ur.9U 0x0040: 72e8 r.13:50:43.885396 IP VM_0_3_centos.cslistener > VM_0_3_centos.33844: Flags [F.], seq 2973795650, ack 608546982, win 357, options [nop,nop,TS val 961901288 ecr 961901288], length 0 0x0000: 0000 0000 0000 0000 0000 0000 0800 4500 ..............E. 0x0010: 0034 f67e 4000 4006 4643 7f00 0001 7f00 .4.~@.@.FC...... 0x0020: 0001 2328 8434 b140 8542 2445 b0a6 8011 ..#(.4.@.B$E.... 0x0030: 0165 fe28 0000 0101 080a 3955 72e8 3955 .e.(......9Ur.9U 0x0040: 72e8 r.13:50:43.885667 IP VM_0_3_centos.33844 > VM_0_3_centos.cslistener: Flags [F.], seq 608546982, ack 2973795651, win 350, options [nop,nop,TS val 961901288 ecr 961901288], length 0 0x0000: 0000 0000 0000 0000 0000 0000 0800 4500 ..............E. 0x0010: 0034 de3f 4000 4006 5e82 7f00 0001 7f00 .4.?@.@.^....... 0x0020: 0001 8434 2328 2445 b0a6 b140 8543 8011 ...4#($E...@.C.. 0x0030: 015e fe28 0000 0101 080a 3955 72e8 3955 .^.(......9Ur.9U 0x0040: 72e8 r.13:50:43.885678 IP VM_0_3_centos.cslistener > VM_0_3_centos.33844: Flags [.], ack 608546983, win 357, options [nop,nop,TS val 961901288 ecr 961901288], length 0 0x0000: 0000 0000 0000 0000 0000 0000 0800 4500 ..............E. 0x0010: 0034 f67f 4000 4006 4642 7f00 0001 7f00 .4..@.@.FB...... 0x0020: 0001 2328 8434 b140 8543 2445 b0a7 8010 ..#(.4.@.C$E.... 0x0030: 0165 fe28 0000 0101 080a 3955 72e8 3955 .e.(......9Ur.9U 0x0040: 72e8 r.我们可以看到,前三个包就是TCP的三次握手,经过了一定量的数据传输之后,最后四个包就是TCP的四次挥手,我们可以用下图来进行表示: ...

July 15, 2019 · 7 min · jiezi

Kong网关部署

Kong是一个使用了lua-nginx-module运行在Nginx之上的Lua应用。Kong是一个成熟的API网关解决方案。API 网关,即API Gateway,是大型分布式系统中,为了保护内部服务而设计的一道屏障,可以提供高性能、高可用的 API托管服务,从而帮助服务的开发者便捷地对外提供服务,而不用考虑安全控制、流量控制、审计日志等问题,统一在网关层将安全认证,流量控制,审计日志,黑白名单等实现。网关的下一层,是内部服务,内部服务只需开发和关注具体业务相关的实现。网关可以提供API发布、管理、维护等主要功能。开发者只需要简单的配置操作即可把自己开发的服务发布出去,同时置于网关的保护之下。 参考文档:https://konghq.com/ (kong官网) https://www.pocketdigi.com/bo...https://www.postgresql.org/ (postgresql官网)http://www.postgres.cn/index....环境:环境:Centos7配置:2c4g权限:root一、安装PostgreSQL注意:请勿使用"yum install kong-community-edition"安装Kong,必须指定版本号!"yum install kong-community-edition-0.14.1.*.noarch.rpm --nogpgcheck"1、配置yum源# 配置完yum库之后卸载之前安装的Postgresqlyum erase postgresql*# 删除遗留的数据rm -rf /var/lib/pgsql2、安装下载RPM(PostgreSQL YUM源),找到对应的版本 CentOS 7 - x86_64 # 安装yum源yum install https://download.postgresql.org/pub/repos/yum/9.6/redhat/rhel-7-x86_64/pgdg-centos96-9.6-3.noarch.rpm# 安装PostgreSQLyum install postgresql96-server postgresql96-contrib3、初始化数据库# 初始化数据库/usr/pgsql-9.6/bin/postgresql96-setup initdb4、PostgreSQL服务控制# PostgreSQL 使用systemctl作为服务托管service postgresql-9.6 start/stop/restart/reload# 或是systemctl start/stop/restart/status postgresql-9.6# 设置开机自启systemctl enable postgresql-9.65、卸载(顺便提供卸载PostgreSQL的命令)# 卸载PostgreSQLyum erase postgresql966、修改密码PostgreSQL数据库默认会创建一个Linux系统用户postgres,通过passwd命令可以设置密码。 # 创建postgres数据库账号su postgrespsqlALTER USER postgres WITH PASSWORD '123456';\qsu root7、设置远程控制7.1 修改vi /var/lib/pgsql/9.6/data/postgresql.conf文件,配置可以远程访问(正式环境按照白名单正确配置)将listen_addresses前的#去掉,并将 listen_addresses = 'localhost' 改成 listen_addresses = '*'; ...

July 14, 2019 · 2 min · jiezi

FFmpeg小点记AVDiscard的作用

声明定义AVDiscard 定义在 avcode.h 中。内容如下: /** * @ingroup lavc_decoding */ enum AVDiscard{ /* We leave some space between them for extensions (drop some * keyframes for intra-only or drop just some bidir frames). */ AVDISCARD_NONE =-16, ///< discard nothing AVDISCARD_DEFAULT = 0, ///< discard useless packets like 0 size packets in avi AVDISCARD_NONREF = 8, ///< discard all non reference AVDISCARD_BIDIR = 16, ///< discard all bidirectional frames AVDISCARD_NONINTRA= 24, ///< discard all non intra frames AVDISCARD_NONKEY = 32, ///< discard all frames except keyframes AVDISCARD_ALL = 48, ///< discard all };上述是FFmpeg v4.1 中的定义。简单的中文翻译下: ...

July 12, 2019 · 2 min · jiezi

PHP源码学习20190408-PHP中include的实现

baiyan 全部视频:https://segmentfault.com/a/11... 回顾while语法的实现<?php$a = 1;while($a){}在上一篇笔记中我们知道,PHP中的while语法所对应的指令执行过程如下图所示: 那么现在回答一下上一篇文章结尾提出的问题:do-while是如何实现的呢?<?php$a = 1;do{ $a = 0;}while($a);经过gdb调试,其最终的指令如下: 第一个ASSIGN对应$a = 1;第二个ASSIGN对应$a = 0;第三个JMPNZ对应do-while循环体,注意这个箭头指向$a = 0对应的ASSIGN指令,代表每次循环都要重新执行一次$a = -这个ASSIGN指令第四个RETURN对应PHP虚拟机自动给脚本添加的返回值include语法的实现我们在面试中经常会被问到如下知识点: - include和require有什么区别?- include和include_once有什么区别(require和require_once)同理)以上两道题的答案相信大家都知道,第一个问题如果文件不存在,include会情况下会发出警告而require会报fatal error并终止脚本运行;而第二个问题中的include_once带有缓存,如果之前加载过这个文件直接调用缓存中的文件,不会去二次加载文件,include_once的性能更好。我们首先看一个例子:1.php:<?php$a = 1;2.php:<?phpinclude "1.php";$b = 2;那么我们通过gdb 2.php并分析它的op_array,可以得出它的指令,一共有3条:ZEND_INCLUDE_OR_EVAL_SPEC_CONST_HANDLER:表示include "1.php";语句ZEND_ASSIGN_SPEC_CV_CONST_RETVAL_UNUSED_HANDLER:表示$b = 2;ZEND_RETURN_SPEC_CONST_HANDLER:表示PHP虚拟机自动给脚本加的返回值接下来我们深入第一个include的handler处理函数:static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INCLUDE_OR_EVAL_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS){ USE_OPLINE zend_op_array *new_op_array; zval *inc_filename; SAVE_OPLINE(); inc_filename = EX_CONSTANT(opline->op1); //这里的op1就是字符串1.php new_op_array = zend_include_or_eval(inc_filename, opline->extended_value); ...这个handler处理函数中核心为zend_include_or_eval()这个函数,它返回一个新的op_array:static zend_never_inline zend_op_array* ZEND_FASTCALL zend_include_or_eval(zval *inc_filename, int type) /* {{{ */{ zend_op_array *new_op_array = NULL; zval tmp_inc_filename; ... } else { switch (type) {{ case ZEND_INCLUDE_ONCE: case ZEND_REQUIRE_ONCE: { //此处带有缓存 zend_file_handle file_handle; zend_string *resolved_path; resolved_path = zend_resolve_path(Z_STRVAL_P(inc_filename), (int)Z_STRLEN_P(inc_filename)); if (resolved_path) { if (zend_hash_exists(&EG(included_files), resolved_path)) { goto already_compiled; } } else { resolved_path = zend_string_copy(Z_STR_P(inc_filename)); } if (SUCCESS == zend_stream_open(ZSTR_VAL(resolved_path), &file_handle)) { if (!file_handle.opened_path) { file_handle.opened_path = zend_string_copy(resolved_path); } if (zend_hash_add_empty_element(&EG(included_files), file_handle.opened_path)) { //加入缓存的哈希表中 zend_op_array *op_array = zend_compile_file(&file_handle, (type==ZEND_INCLUDE_ONCE?ZEND_INCLUDE:ZEND_REQUIRE)); zend_destroy_file_handle(&file_handle); zend_string_release(resolved_path); if (Z_TYPE(tmp_inc_filename) != IS_UNDEF) { zend_string_release(Z_STR(tmp_inc_filename)); } return op_array; } else { zend_file_handle_dtor(&file_handle);already_compiled: new_op_array = ZEND_FAKE_OP_ARRAY; } } else { if (type == ZEND_INCLUDE_ONCE) { zend_message_dispatcher(ZMSG_FAILED_INCLUDE_FOPEN, Z_STRVAL_P(inc_filename)); } else { zend_message_dispatcher(ZMSG_FAILED_REQUIRE_FOPEN, Z_STRVAL_P(inc_filename)); } } zend_string_release(resolved_path); } break; case ZEND_INCLUDE: case ZEND_REQUIRE: new_op_array = compile_filename(type, inc_filename); //关键调用 break; ... return new_op_array;}在ZEND_INCLUDE_ONCE分支中可以观察到,如果是include_once或者require_once的case,会先去缓存中查找,那么这个缓存是怎么实现的呢?最容易想到的就是哈希表,key为文件名,value为文件内容,这样就可以直接从缓存中读取文件,不用再次加载文件了,提高效率。我们回到主题include语法,在ZEND_INCLUDE分支中我们可以看到,这里又调用了一个新的函数compile_filename(),它返回一个新的op_array。因为include是包含另一个外部文件,而op_array是一个脚本的指令集,所以需要新创建一个op_array,存储另外一个文件的指令集,我们继续跟进compile_filename():zend_op_array *compile_filename(int type, zval *filename){ zend_file_handle file_handle; zval tmp; zend_op_array *retval; zend_string *opened_path = NULL; ... retval = zend_compile_file(&file_handle, type); //核心调用 return retval;}这个函数中还会继续调用zend_compile_file()函数,它是一个函数指针,指向compile_file()函数:ZEND_API zend_op_array *compile_file(zend_file_handle *file_handle, int type){ ... zend_op_array *op_array = NULL; if (open_file_for_scanning(file_handle)==FAILURE) { ... } else { op_array = zend_compile(ZEND_USER_FUNCTION); //核心调用 } return op_array;}我们可以看到,它最终调用了zend_compile函数。我们是不是对它很熟悉呢?没错,它就是PHP脚本编译的入口。随后,通过调用这个函数,就可以对引入的外部脚本1.php进行词法分析和语法分析等编译操作了。现在思考一个问题,这个函数返回一个op_array,是引入的新的外部脚本1.php的op_array,那么原来的旧脚本2.php的op_array的状态和数据应该如何存储呢?答案是继续往zend_execute_data栈中添加。当include脚本执行完成之后,出栈即可。同递归的原理一样,递归也是借助栈,当你不断递归的时候,数据不断入栈,到最后的递归终止条件的时候,逐步出栈即可,所以递归是非常慢的,效率极低。其他PHP脚本的执行流程我们之前讲过,PHP脚本的执行入口为main函数(我们代码层面无法看到,是虚拟机帮助我们加的)。从main函数进去之后,PHP脚本的执行总共有5大阶段:CLI模式(command line interface,即命令行模式。如在命令行下执行脚本:php 1.php):php_module_startup:模块初始化php_request_startup:请求初始化php_execute_script:执行脚本php_request_shutdown:请求关闭php_module_shutdown:模块关闭CLI模式下,运行一次就会直接退出,并不常驻内存,接下来看一下我们使用的最多的FPM模式,它常驻内存。一次请求到来,PHP-FPM就要对其进行处理,所以在 php_request_startup、php_execute_script、php_request_shutdown三个阶段会进行死循环,让PHP-FPM常驻内存,才能不断地处理一个个到来的请求。但是这样会有一个问题,每一个请求到来的时候,都会重新进行词法解析、语法解析......效率是非常低的。为了解决这个问题,PHP中我们常说的opcache就要粉墨登场了。它会把之前解析过的opcode缓存起来,下一次再遇到相同opcode的时候,就不用再次解析,提升性能。初探nginx+php-fpm架构在LNMP架构下,前端的请求发来,先会通过nginx做代理,然后通过fastcgi协议,转发给上游的php-fpm,由php-fpm真正地处理请求。我们知道,nginx是多进程架构的反向代理web服务器,由一个master进程和多个worker进程组成:master进程:管理所有worker进程(如worker进程的创建、销毁)worker进程:负责处理客户端发来的请求当杀死master进程的时候,worker进程依然存在,可以为客户端提供服务当杀死worker进程的时候(且当前没有其他worker进程),master进程就会再创建worker进程,保证nginx服务正常运行下一篇文章我们就即将讲解fastcgi协议,逐步揭开nginx+php-fpm架构通信的神秘面纱

July 12, 2019 · 2 min · jiezi

sonic-orchagent与syncd之间的请求与应答

syncd进程是介于orchagent与driver之间的进程。syncd从asic-db中读取的数据经转换后调用驱动提供的sai接口进行下硬件,同时需要将驱动的应答进行一定的处理,还需要处理驱动的事件通知(比如端口up/down,mac老化等信息)。处理的消息如下图所示: orchagent与syncd之间的操作orchagent与syncd之间会进行如下几种操作: create:创建一个对象remove:删除一个对象set:设置对象属性get:获取对象属性notify:driver事件通知对于create,remove,set请求,orchagent会在sairedis层构建一个虚拟的sai层:sairedis。orchagent执行sai接口只是对asic-db进行操作,生成或者删除虚拟对象(vid)。默认所有操作都是成功的,直接返回,不等待syncd的应答。执行上图的1和6。syncd从asic-db中读出请求执行上图的2,3,4。如果4步骤返回成功,则整个请求运行结束,否则syncd将会发送shutdown通知给orchagent。orchagent会退出,如上图的5,6. 对于get操作,orchagent执行1后会使用select阻塞等待syncd的应答,如果syncd在60分钟内没有应答,那么orchagent会产生segment退出。get操作执行顺序为1->2->3->4->5->6。 对于driver的notify,orchagent会在主进程的select中监听asic-db。驱动检测到硬件事件后,调用syncd注册的回调函数通知syncd。syncd中有一个专门处理driver-notify的线程ntf-thread。ntf-thread解析driver的notify,然后通过asic-db通知orchagent。执行顺序7->8->9。 注:orchagent与syncd关于sai这一层非常相似。它们会调用大量的同名函数。这些函数只是名字相同,orchagent调用的是sai-redis库中的函数,而syncd调用的是driver提供的sai库 get操作阻塞等待orchagent执行sai的get操作时会调用到redis_generic_get函数。 std::shared_ptr<swss::ConsumerTable> g_redisGetConsumer;sai_status_t redis_generic_get( _In_ sai_object_type_t object_type, _In_ sai_object_id_t object_id, _In_ uint32_t attr_count, _Out_ sai_attribute_t *attr_list){ SWSS_LOG_ENTER(); std::string str_object_id = sai_serialize_object_id(object_id); return internal_redis_generic_get( object_type, str_object_id, attr_count, attr_list);}sai_status_t internal_redis_generic_get( _In_ sai_object_type_t object_type, _In_ const std::string &serialized_object_id, _In_ uint32_t attr_count, _Out_ sai_attribute_t *attr_list){ SWSS_LOG_ENTER(); /* * Since user may reuse buffers, then oid list buffers maybe not cleared * and contain som garbage, let's clean them so we send all oids as null to * syncd. */ clear_oid_values(object_type, attr_count, attr_list); std::vector<swss::FieldValueTuple> entry = SaiAttributeList::serialize_attr_list( object_type, attr_count, attr_list, false); std::string str_object_type = sai_serialize_object_type(object_type); std::string key = str_object_type + ":" + serialized_object_id; SWSS_LOG_DEBUG("generic get key: %s, fields: %lu", key.c_str(), entry.size()); if (g_record) { recordLine("g|" + key + "|" + joinFieldValues(entry)); } // get is special, it will not put data // into asic view, only to message queue // 写入本次get事件 g_asicState->set(key, entry, "get"); // wait for response // 创建临时 select swss::Select s; // 添加事件 s.addSelectable(g_redisGetConsumer.get()); //循环等待syncd的应答 while (true) { SWSS_LOG_DEBUG("wait for response"); swss::Selectable *sel; //阻塞等待,时间为GET_RESPONSE_TIMEOUT int result = s.select(&sel, GET_RESPONSE_TIMEOUT); //只处理应答情况OBJECT if (result == swss::Select::OBJECT) { swss::KeyOpFieldsValuesTuple kco; g_redisGetConsumer->pop(kco); const std::string &op = kfvOp(kco); const std::string &opkey = kfvKey(kco); SWSS_LOG_DEBUG("response: op = %s, key = %s", opkey.c_str(), op.c_str()); if (op != "getresponse") // ignore non response messages { continue; } sai_status_t status = internal_redis_get_process( object_type, attr_count, attr_list, kco); if (g_record) { const std::string &str_status = kfvKey(kco); const std::vector<swss::FieldValueTuple> &values = kfvFieldsValues(kco); // first serialized is status recordLine("G|" + str_status + "|" + joinFieldValues(values)); } SWSS_LOG_DEBUG("generic get status: %d", status); return status; } SWSS_LOG_ERROR("generic get failed due to SELECT operation result: %s", getSelectResultAsString(result).c_str()); break; } //超时和异常都返回SAI_STATUS_FAILURE if (g_record) { recordLine("G|SAI_STATUS_FAILURE"); } SWSS_LOG_ERROR("generic get failed to get response"); return SAI_STATUS_FAILURE;}对于get操作,当syncd比较忙的时候,极端情况下会导致orchagent异常退出。 ...

July 11, 2019 · 7 min · jiezi

RPA来了CFO有话要说

随着RPA(机器人流程自动化)技术的兴起与发展,流程自动化对公司业务开始产生更为重大的意义,特别是对于公司业务效率与准确性的提升方面,以及成本节省方面,RPA有着更大的优势与应用前景。 过去,人们评判一名CFO合格的标准,主要看其是否具备专业的财务背景和优秀的管理能力。当然,要想做的更为出色,还需要具备良好的沟通、协调、创新能力,以及高情商、高忍耐力、高灵活性等素质。 不可否认,这些因素对于评判一名CFO是否合格或优秀,如今依然凑效。但只有这些能力还远远不够,特别是在科技高速发展的今天,一名出色的CFO一定要对财务范围之外的创新技术有所了解。据致同调查研究显示,约有89%的CFO认为未来这一职位需要更强的数据分析技能。而这一观点的形成则来源于目前人工智能的应用以及大数据的普及。 另有调查称,61%的CFO认为使用数据及分析工具协助进行业务决策是他们财务战略中极为重要的一部分;同时,52%的CFO认为向数字化运营模式转型也是非常重要的环节。 未来1-5年内可以应用流程自动化的领域包括财务规划与分析,预算与预测,企业发展与战略规划以及财务报告与管理。 在实际的工作中,被提及最多的问题就是,“究竟什么样的场景”、“可以应用到RPA相关技术?”。RPA可以在很多职能部门中发挥作用,最能够凸显收益的就是将之应用于需要大量重复性劳动的工作中,或是有明确规则可以依据的工作中。通过应用RPA,效率一般可以提升4-5倍。 这是因为RPA具备,高效、合规、节省成本、能提升准确性和生产力等优势。 更为重要的是,RPA可以将员工的时间释放出来,让员工把时间和关注点放在更有价值的事情上——包括重新规划工作内容,更专注地解决产品组合、利润创造以及产品定价等机器人无法替代的工作上。为了能够最大限度地发挥RPA的功能,并为公司带来更为广阔的发展空间,CFO们必须要对RPA技术具备深入的了解。这样才能对接下来的工作有着更为清楚的认识和更为妥善的安排,优先考虑能够给公司带来最佳效益的部门,进行RPA的部署,依靠RPA解决企业痛点。

July 11, 2019 · 1 min · jiezi

sonic-orch调度系统1select

sonic orch调度系统之----select 常见的服务器模型有多进程模型,多线程,IO多路复用,协程等模型。sonic的核心守护进程orchagent采用的是IO多路复用模型,早期的sonic采用的是select实现多路复用,后面的版本采用的是epoll。使用select(跟多路复用的select名字一样)类对底层进行了封装,屏蔽了差异。 class Selectable事件基类,描述了epoll事件,可以是读,写,异常等事件。该结构对通用epoll事件进行了封装,真实事件通过该类派生出来,比如redis数据库事件:class RedisSelect : public Selectable;netlink事件:class NetLink : public Selectable;通知:class NotificationConsumer : public Selectable,orch执行单元:class Executor : public Selectable,定时器:class SelectableTimer : public Selectable等。 class Selectable{public: Selectable(int pri = 0) : m_priority(pri), m_last_used_time(std::chrono::steady_clock::now()) { lastusedsequence = g_lastusedsequence++;} virtual ~Selectable() = default; /* return file handler for the Selectable */ virtual int getFd() = 0; /* Read all data from the fd assicaited with Selectable */ virtual void readData() = 0; /* true if Selectable has data in its cache */ // 判断是否还有数据,如果有放入就绪事件set virtual bool hasCachedData() { return false; } /* true if Selectable was initialized with data */ // 判断是否需要读取初始数据 virtual bool initializedWithData() { return false; } /* run this function after every read */ // 更新事件数 virtual void updateAfterRead() { } int getPri() const { return m_priority; }private: friend class Select;//友元类为Select // only Select class can access and update m_last_used_time std::chrono::time_point<std::chrono::steady_clock> getLastUsedTime() const { return m_last_used_time; } // 最后使用序列号 unsigned long getLastUsedsequence() const { return lastusedsequence; } // 跟新最后使用序列号 void updateLastUsedTime() { m_last_used_time = std::chrono::steady_clock::now(); lastusedsequence = g_lastusedsequence++; } // 优先级,实现基于优先级调度 int m_priority; // defines priority of Selectable inside Select // higher value is higher priority std::chrono::time_point<std::chrono::steady_clock> m_last_used_time; unsigned long lastusedsequence;//上次使用序列号 static unsigned long g_lastusedsequence;//全局基准序列号,用于对同优先级业务进行公平调度};class Selectclass Select{public: Select(); ~Select(); /* Add object for select 给epoll添加一个事件 */ void addSelectable(Selectable *selectable); /* Remove object from select 删除一个epoll事件 */ void removeSelectable(Selectable *selectable); /* Add multiple objects for select 添加多个epoll事件 */ void addSelectables(std::vector<Selectable *> selectables); enum {//返回的事件类型 OBJECT = 0, ERROR = 1, TIMEOUT = 2, }; //执行epoll int select(Selectable **c, unsigned int timeout = std::numeric_limits<unsigned int>::max()); int select(std::vector<Selectable *> &vc, unsigned int timeout = std::numeric_limits<unsigned int>::max());private: //epoll事件比较函数,通过该函数实现事件的优先级 struct cmp { bool operator()(const Selectable* a, const Selectable* b) const { /* Choose Selectable with highest priority first */ if (a->getPri() > b->getPri()) return true; else if (a->getPri() < b->getPri()) return false; /* if the priorities are equal */ /* use Selectable which was selected later */ if (a->getLastUsedsequence() < b->getLastUsedsequence()) return true; else if (a->getLastUsedsequence() > b->getLastUsedsequence()) return false; /* when a == b */ return false; } }; //epoll轮询函数 int poll_descriptors(Selectable **c, unsigned int timeout); int poll_descriptors(std::vector<Selectable *> &vc, unsigned int timeout); int m_epoll_fd;//epoll句柄 std::unordered_map<int, Selectable *> m_objects;//监听的事件句柄与其对应的selectable之间的关系 std::set<Selectable *, Select::cmp> m_ready;//已经就绪的事件集合,提供了比较函数,从而实现优先级调度};Select::Select()Select::Select(){ m_epoll_fd = ::epoll_create1(0);//创建epoll句柄 if (m_epoll_fd == -1) { std::string error = std::string("Select::constructor:epoll_create1: error=(" + std::to_string(errno) + "}:" + strerror(errno)); throw std::runtime_error(error); }}Select::~Select()Select::~Select(){ (void)::close(m_epoll_fd);}void Select::addSelectable(Selectable *selectable)void Select::addSelectable(Selectable *selectable){ const int fd = selectable->getFd(); if(m_objects.find(fd) != m_objects.end())//已经添加了该事件,退出 { SWSS_LOG_WARN("Selectable is already added to the list, ignoring."); return; } m_objects[fd] = selectable; if (selectable->initializedWithData())//是否已经有数据可读,读出已有的数据 { m_ready.insert(selectable); } //添加可读事件 struct epoll_event ev = { .events = EPOLLIN, .data = { .fd = fd, }, }; int res = ::epoll_ctl(m_epoll_fd, EPOLL_CTL_ADD, fd, &ev); if (res == -1) { std::string error = std::string("Select::add_fd:epoll_ctl: error=(" + std::to_string(errno) + "}:" + strerror(errno)); throw std::runtime_error(error); }}void Select::removeSelectable(Selectable *selectable)void Select::removeSelectable(Selectable *selectable){ const int fd = selectable->getFd(); m_objects.erase(fd); m_ready.erase(selectable); //从epoll中删除事件 int res = ::epoll_ctl(m_epoll_fd, EPOLL_CTL_DEL, fd, NULL); if (res == -1) { std::string error = std::string("Select::del_fd:epoll_ctl: error=(" + std::to_string(errno) + "}:" + strerror(errno)); throw std::runtime_error(error); }}void Select::addSelectables(vector<Selectable *> selectables)void Select::addSelectables(vector<Selectable *> selectables){ for(auto it : selectables)//添加多个事件 { addSelectable(it); }}int Select::poll_descriptors(......)提取一个就绪事件 ...

July 10, 2019 · 5 min · jiezi

黑马程序员告诉你计算机专业的学生该如何提高自己

首先C语言是要学好的,基础语法是很必须的。比如:数组、指针,结构体这些。指向指针,指针数据,数组指针这些概念都要很清楚。并且试着用C写一个四则运算的小程序。大学的时候我们应该会刷很多的oj的题,那么常见的八皇后之类的问题基本要写的出来。 C语言是打基础,国内很多计算机专业上来就是C语言。虽然有一些难度,但是学习编程最好的入门语言绝对是C,因为学好C你想在接触别的,会发现很容易理解。 C语言的基础扎实之后,不需要把所有你们学过的学科都掌握。停下来看看自己今后的发展方向,你是要做前端还是后端,或者是游戏方面的,还是偏网络攻防这类的工作。 如果你今后的发展方向是软件开发,那么建议你学Java;IIS7站长 如果以后的职业发展规划是大数据或者是人工智能相关的,那么建议你学Python; 如果未来你想做的事情有关游戏开发的,那么就学C++; 如果你想做前端开发的话,你就需要学号JS,PHP这类的语言 Java是目前来说,所有语言当中市场需求量最大的语言,就用C#来比较,实际上C#可以涉及到的岗位更多,但是需求量来比还是Java略胜。所以不管你是大学计算机专业的学生,还是要转行到编程的同学。都一定要找到自己的职业发展方向。

July 9, 2019 · 1 min · jiezi

数据结构算法学习排序归并排序

排序讲一组有顺序的元素按大小(只要定义可以返回true或false的比较关系,非一定数值比较)重新调整顺序。 归并排序归并排序是分而治之策略,每次把两个已经排序的数组按大小关系。算法实现采用了递归实现,依次将数组长度(1,2...n/4,n/2,n)内的元素排序合并。 算法实现void merge(long long int* arr, long long int* tmp, int left, int right, int rightEnd) { int i, leftEnd, num, tmpPos; leftEnd = right - 1; tmpPos = left; num = rightEnd - left + 1; while(left <= leftEnd && right <= rightEnd) { if (arr[left] <= arr[right]) { tmp[tmpPos++] = arr[left++]; } else { tmp[tmpPos++] = arr[right++]; } } while(left <= leftEnd) { tmp[tmpPos++] = arr[left++]; } while(right <= rightEnd) { tmp[tmpPos++] = arr[right++]; } for (i = 0; i < num; i++, rightEnd--) { arr[rightEnd] = tmp[rightEnd]; }}void mSort(long long int* arr, long long int* tmp, int left, int right, int len) { int center; int i; if (left < right) { center = (left + right) / 2; mSort(arr, tmp, left, center, len); mSort(arr, tmp, center + 1, right, len); merge(arr, tmp, left, center + 1, right); printf("合并参数:%d %d 结果:", left, right); for (i = 0; i < len; i++) { printf("%lld ", arr[i]); } printf("\n"); }}long long int* elrSortMerge(long long int* arr, int len) { long long int* tmp; tmp = malloc(len * sizeof(long long int)); if (tmp) { mSort(arr, tmp, 0, len - 1, len); free(tmp); } else { printf("no space for sort merge\n"); } return arr;}调试调用#include <stdio.h>#include <stdlib.h>#include "elr_sort_merge.h"int main(int argc, char **argv){ int i; long long int arr[] = {6, 2, 4, 1, 3, 5, 0, 8, 9, -1, 7}; elrSortMerge(arr, 11); printf("%d\n", (int)(sizeof(arr) / sizeof(long long int))); for (i = 0; i < 11; i++) { printf("%lld ", arr[i]); } printf("\n"); return 0;}输出合并参数:0 1 结果:2 6 4 1 3 5 0 8 9 -1 7 合并参数:0 2 结果:2 4 6 1 3 5 0 8 9 -1 7 合并参数:3 4 结果:2 4 6 1 3 5 0 8 9 -1 7 合并参数:3 5 结果:2 4 6 1 3 5 0 8 9 -1 7 合并参数:0 5 结果:1 2 3 4 5 6 0 8 9 -1 7 合并参数:6 7 结果:1 2 3 4 5 6 0 8 9 -1 7 合并参数:6 8 结果:1 2 3 4 5 6 0 8 9 -1 7 合并参数:9 10 结果:1 2 3 4 5 6 0 8 9 -1 7 合并参数:6 10 结果:1 2 3 4 5 6 -1 0 7 8 9 合并参数:0 10 结果:-1 0 1 2 3 4 5 6 7 8 9

July 7, 2019 · 2 min · jiezi

MiniFly四轴学习系列2四轴部分main函数

**说明:本文原创作者『Allen5G』首发于微信公众号『Allen5G』,关注获取更多干货!标签:编程,软件,算法,思维** 先看看整个工程的任务关系手机APP是需要自己开发APP的,暂时没空搞,坐等官方。。 然后主要就是上位机和遥控器的使用了上位机通过USB连接四轴,需要烧录固件BootLoader遥控器使用NFR无线通信,这个也是可玩性最高的地方,当然也是最核心的部分---姿态解算和姿态控制,之后主要要研究的就是这块代码 整个系统是基于Freertos系统,所以函数也是在系统中进行任务切换 看下main函数部分:首先说下代码风格很喜欢,可能是强迫症,注释必须对其 #include "system.h" /*头文件集合*/TaskHandle_t startTaskHandle; //创建句柄static void startTask(void *arg);//声明创建函数int main() //主函数{ systemInit(); /*底层硬件初始化*/ xTaskCreate(startTask, "START_TASK", 300, NULL, 2, &startTaskHandle); /*创建起始任务*/ vTaskStartScheduler(); /*开启任务调度*/ while(1){};}/*创建任务*/void startTask(void *arg){ taskENTER_CRITICAL(); /*进入临界区*/ xTaskCreate(radiolinkTask, "RADIOLINK", 150, NULL, 5, NULL); /*创建无线连接任务*/ xTaskCreate(usblinkRxTask, "USBLINK_RX", 150, NULL, 4, NULL); /*创建usb接收任务*/ xTaskCreate(usblinkTxTask, "USBLINK_TX", 150, NULL, 3, NULL); /*创建usb发送任务*/ xTaskCreate(atkpTxTask, "ATKP_TX", 150, NULL, 3, NULL); /*创建atkp发送任务任务*/ xTaskCreate(atkpRxAnlTask, "ATKP_RX_ANL", 300, NULL, 6, NULL); /*创建atkp解析任务*/ xTaskCreate(configParamTask, "CONFIG_TASK", 150, NULL, 1, NULL); /*创建参数配置任务*/ xTaskCreate(pmTask, "PWRMGNT", 150, NULL, 2, NULL); /*创建电源管理任务*/ xTaskCreate(sensorsTask, "SENSORS", 450, NULL, 4, NULL); /*创建传感器处理任务*/ xTaskCreate(stabilizerTask, "STABILIZER", 450, NULL, 5, NULL); /*创建姿态任务*/ xTaskCreate(wifiLinkTask, "WIFILINK", 150, NULL, 4, NULL); /*创建通信连接任务*/ xTaskCreate(expModuleMgtTask, "EXP_MODULE", 150, NULL, 1, NULL); /*创建扩展模块管理任务*/ printf("Free heap: %d bytes\n", xPortGetFreeHeapSize()); /*打印剩余堆栈大小*/ vTaskDelete(startTaskHandle); /*删除开始任务*/ taskEXIT_CRITICAL(); /*退出临界区*/}void vApplicationIdleHook( void ){ static u32 tickWatchdogReset = 0; portTickType tickCount = getSysTickCnt(); if (tickCount - tickWatchdogReset > WATCHDOG_RESET_MS) //添加独立看门狗 { tickWatchdogReset = tickCount; watchdogReset(); } __WFI(); /*进入低功耗模式*/}main函数正常步骤:先是硬件初始化:基本都是32的基础操作,后边看哪部分时涉及到了再说吧! ...

July 7, 2019 · 2 min · jiezi

什么是程序

虽然在这里我们主要关注的是操作系统,但毕竟操作系统的目的就是能更好的运行用户程序,而且该教程主要是站在程序员的角度来讲解操作系统的,作为程序员是绕不开程序这个话题的。因此在深入理解操作系统之前还是有必要来聊一聊程序是怎么一回事。 那究竟是什么是程序呢?我们引用Wikipedia对计算机程序的定义: A computer program is a collection of instructions that performs a specific task when executed by a computer.翻译成大白话就是,能指挥计算机干活的一堆指令就叫计算机程序。那怎样才能写出指挥计算机干活的指令呢?这就涉及到程序是怎么来的。 程序是怎么来的呢?程序是我们伟大的可敬的广大程序员们用编程语言一个字符一个字符写出来的文本字符串,只不过这些文本字符串是人类可以认识的。无论用的什么程序语言,C/C++、Java、Python、JavaScript、C#、Perl、Lua、Shell、汇编语言等等等等,只要是你写出来的文本字符串能指挥计算机干活,这都叫程序。这些程序员认识的文本字符串就是可以指挥计算机完成特定任务的指令。你可能有点糊涂了,这些文本字符串真的就是计算机能用来完成特定任务的指令吗?计算机不是只认识0和1这两个数字吗? 天才的榆木疙瘩计算机其实是一个数学学得非常差的家伙,以至于差到只能认识两个数,0和1,其它的就都不能认识了,我们要面对的就是这样一个榆木疙瘩。 虽然这个榆木疙瘩数学不好识数不多,但是这家伙有一个我们人类难以望其项背的能力,不,对于人类来说简直就是超能力,那就是这个榆木疙瘩算数非常快,对于简单的加法我们人类可能一般一秒能算不超过10个,但是计算机一秒可以完成数十亿次的计算,简直是天才!虽然计算机能认识的数就只有0和1这么简单,但是其计算速度体现出了简单的威力。 从文本字符串到机器指令至此,我们知道程序员(人类)和计算机是两个完全不同的物种,不同的物种能理解的语言是完全不同的,就好比普通的人不会明白一群鸟语在说什么一样,我们人类也不能打开vim或者宇宙无敌IDE——Visual Studio直接写0和1吧(虽然这是可以的,早期的程序员确实就是这么干的,牛不牛)。 因此,我们需要某种魔法把人类认识的C/C++、Java、Python之类的翻译出计算机可以认识二进制01指令。这样的魔法就来自两个东西,编译器和解释器。 翻译官编译器和解释器编译器大家应该都比较熟悉,我们写好C/C++程序后第一步就是编译,这里编译工作就是编译器来完成的。你可以简单的理解为编译器把C/C++程序直接翻译成计算机可以认识的01二进制机器指令。 对于解释器有的同学可能就不是那么熟悉了,写Java、Python、C#程序的时候你从没有听说过要“编译一下Java,编译一下Python,编译一下C#”吧。你可能会说C/C++程序我能理解了,编译器把C/C++程序直接翻译成了01二进制机器指令,那Java、Python一类的程序是怎么运行的呢? 大家可以想一想,你在写Python、Java、C#程序之前是不是要安装一堆东西,称之为“运行时环境”? 如果你想不起来,赶紧重新搭一套环境试试是不是这么回事。 这里的运行时环境其实就是解释器。你可以把这个解释器简单的理解为就是一个程序,只不过。。。注意注意!!!前方高能!!!,只不过是解释器这个程序可以运行你写的Java、Python、C#的程序,解释器是一个可以运行程序的程序!!!那解释器这个程序又是怎么来的呢?一般情况下这些解释器其实是用C/C++写出来的。只不过用C/C++写的这个程序专门用来执行你写的Java、Python之类的程序,高能完毕。 伟大的C语言希望到目前为止你还没有晕,到这里我们知道了,不管我们用的是Java、Python、JavaScript什么的也好最终都逃不出C/C++(Go等语言除外),CPU不直接执行Java、Python、JavaScript之类的解释型语言程序,CPU可以直接执行的是解释器代码,解释器最终来执行Java等程序,这就是解释型语言效率不如编译型语言效率高的原因。因为C/C++程序最终被编译器翻译成了01机器指令,CPU可以直接运行运行机器指令,而对于解释型语言来说CPU首先执行的是解释器的程序,然后解释器再执行你写的程序,性能上当然不及编译型语言。 而C++程序其实在编译过程中也会转化为C程序然后再转为01二进制机器指令,并且们使用的Windows、Linux、MacOS等操作系统同样是用C语言来编写的,从最底层的操作系统到上层的应用程序实际上都逃脱不了C语言。 从这个角度看,C语言真是一门伟大的语言。 回到操作系统饶了一大圈我们回到操作系统,接下来关于操作系统的讲解中涉及到示例程序没有明确说明的话指的是C语言程序。请注意,如果你对C语言不熟悉也没有关系,我们示例都非常简单不会涉及到复杂的C语言相关概念与用法,有任何语言的使用经验都可轻松应对。 C语言程序编译好后生成的可执行程序在Windows中就是我们熟悉的exe程序,在Linux下是elf程序,这些可执行程序编译好后和普通文件一样存放在磁盘当中。 在接下来关于操作系统的讨论当中,没有明确说明的话,以下几个词汇,"程序","用户程序","应用程序"指的都是编译好后放在磁盘上的可执行程序。 操作系统也是程序一定要认识到,操作系统也是程序,只不过这个程序不是简单的往屏幕上打印helloworld,不能用来文字语音视频聊天,不能用来上网,不能用来看电影,不能用来玩游戏。那么这个貌似什么娱乐设施都提供不了的程序有什么用呢?这个程序的作用无比重要,该程序的作用是为以上用户程序提供一个良好的运行环境,管理计算机硬件资源包括:CPU、内存、磁盘、网卡、外设等等等等,这个程序就是该教程重点关注的操作系统。 总结这是该教程的第一节,在这一节中我们从各个方面讲解了程序这一话题。 程序分为编译型程序,比如C/C++,以及解释型程序比如Java、Python、JavaScript等。编译型程序被编译器直接翻译成CPU可以直接运行的机器指令,而解释型程序无需编译,其运行依靠的是解释器,解释器是一个可以执行程序的程序,解释器这个程序一般是由C/C++程序编写的。 需要我们注意的是操作系统也是一个程序,只不过这个程序的作用比较特殊,这个程序是用来管理计算机系统中各种软硬件资源的,比如提供进程、线程机制,管理CPU等等,这个程序也是接下来该教材的主角。 更多计算机内功文章,欢迎关注微信公共账号:码农的荒岛求生。 计算机内功决定程序员职业生涯高度

July 6, 2019 · 1 min · jiezi

MiniFly四轴学习系列1四轴部分系统及电源分析

最近硬件四轴很火,了解了很久,还是选择了正点原子的MiniFly,主要还是原子的论坛资料多,后边可以有人讨论,不像很多就是建了个群,研究问题还是在论坛方便很多。 四轴终于拿到手,功能很强大,主要是还支持二次开发,可以研究下玩玩小四轴了 还是先从硬件入手分析下 1.系统框架主体可以分成两个小系统,后续可以按照这个顺序分析代码1.通信部分---基于M0和NFR51822,包括电源管理2.控制部分---基于M4和外围传感器 2.比较关注的电源部分电源还是比较考究,直接包含了充电功能,不用每次使用外置的充电模块(之前自制过一个充电模块,看来以后可以集成到电路里了) 按键断电功能这个比较喜欢:这个是NFR51822控制VEN_D引脚来实现断电的 上个NFR51822部分电路 开始看代码,大致看了下程序,先是监测按键状态 /*按键扫描处理*/void buttonProcess(){    static unsigned int lastTick;    static unsigned int pressedTick;    static bool pressed = false;    if (lastTick != systickGetTick())    {        lastTick = systickGetTick();        if(pressed==false && BUTTON_READ()==BUTTON_PRESSED)        {            pressed = true;            pressedTick = systickGetTick();        }        else if(pressed==true)        {            if(BUTTON_READ()==BUTTON_RELEASED)                pressed = false;            if ((systickGetTick()-pressedTick) > BUTTON_LONGPRESS_TICK)                state = buttonLongPress;            else if(BUTTON_READ()==BUTTON_RELEASED)                state = buttonShortPress;        }    }}/*获取按键状态*/buttonEvent_e buttonGetState(){    buttonEvent_e currentState = state;    state = buttonIdle;    return currentState;}然后就是在主函数中进行相应的操作,先不说了 看下怎么实现电源开关的:这么最后应该F1部分还是有电的,只是消耗较少,寻求一种可以完全断电的方案?? /*开启电源*/void pmPowerOn(void){    nrf_gpio_cfg_output(UART_TX_PIN);   //使能串口TX    nrf_gpio_pin_set(UART_TX_PIN);        nrf_gpio_cfg_output(RADIO_PAEN_PIN);// 开启无线功能    nrf_gpio_pin_set(RADIO_PAEN_PIN);        nrf_gpio_cfg_output(PM_VBAT_SINK_PIN);// 设置ADC    nrf_gpio_pin_clear(PM_VBAT_SINK_PIN);    pmStartAdc();   //开启ADC转换}/*关闭电源*/void pmPowerOff(void){       nrf_gpio_cfg_input(UART_TX_PIN, NRF_GPIO_PIN_PULLDOWN);    uartDeinit();        nrf_gpio_cfg_input(STM_NRST_PIN, NRF_GPIO_PIN_PULLDOWN);    nrf_gpio_pin_clear(STM_NRST_PIN);        nrf_gpio_pin_clear(PM_VCCEN_PIN);     //关闭 LDO使能控制脚        LED_OFF();      nrf_gpio_pin_clear(RADIO_PAEN_PIN);   //关闭PA    nrf_gpio_cfg_input(PM_VBAT_SINK_PIN, NRF_GPIO_PIN_NOPULL);    NRF_POWER->SYSTEMOFF = 1UL;    while(1);  //进入死循环}void pmInit(){       /* STM32 电源配置 */    nrf_gpio_cfg_output(PM_VCCEN_PIN);    nrf_gpio_pin_set(PM_VCCEN_PIN);     //使能stm32电源    msDelay(100);        /* STM32 复位 */    nrf_gpio_cfg_output(STM_NRST_PIN);    nrf_gpio_pin_clear(STM_NRST_PIN);    msDelay(100);       nrf_gpio_pin_set(STM_NRST_PIN);    msDelay(100);       nrf_gpio_cfg_input(USB_CONNECTED_PIN, NRF_GPIO_PIN_NOPULL);    nrf_gpio_cfg_input(PM_CHG_STATE_PIN, NRF_GPIO_PIN_PULLUP);        pmPowerOn(); //开启电源}

July 6, 2019 · 1 min · jiezi