嵌入式Hacker (es-hacker)

Embedded bsp developer enjoys thinking and hacking opensource and develop boards(NanoPi, LicheePi, RPi...)

0%

哈喽,我是杰克吴,继续记录我的学习心得。

一、关于兴趣的几点思考

1. 享受不是兴趣,愿意付出才是:

  • 兴趣很容易跟享受混淆。享受是被动的,无需付出;而兴趣则要求你甘愿为了这件事情付出努力。

2.任何事情,接触皮毛的时候不要谈兴趣:

  • 在我开始公众号写文章之前,只是粗浅地觉得这个事不难我可以尝试一下,而事实上,持续写作的难度和意义超乎大多数人的想象。

  • 任何事情,先做到 60 分,再谈是否喜欢

3. 兴趣和爱好不太一样:

  • 区别在于你是否需要且愿意通过刻意练习以收获这个兴趣,以及这件事是否能给你带来持续的成就感。

  • 吃喝玩乐(旅游,逛街,买买买)是爱好,不是兴趣。纯粹的看电影是爱好,但是认真地写影评(经历了思考与分享)则算是兴趣。表面看上去都是同一件事,但是不同人会发展成不一样的结果

  • 最开始时可能只是爱好,但是随着你的持续思考和投入,可能会发展为你的理想职业

4. 兴趣可以带有功利性:

  • 那些看似功利的标准(例如高考、面试),存在很多偏差的部分,但不可否认,在绝大多数情况下,它们提供了较为高效和正确的努力方向

  • 把自己热爱的事情用来挣钱,非常好。只凭自己的兴致去做,确实会有更多愉悦,但这也是最廉价、最轻易的喜欢了,问题是,你很难真正做得好。你真的喜欢这个事,你会主动争取做好,赢得市场才会给你带来更长久的愉悦感


二、模块机制快速入门 (1)

目录:

1
1. 内核模块的使用
2
2. 内核模块的文件格式
3
3. EXPORT_SYMBOL 是如何实现符号导出的?
4
4. 相关参考
阅读全文 »

哈喽,周二愉快。我是老吴,继续记录我的学习心得。

一、实现目标的价值

对于实现目标来说,价值有三种来源:

  • 成就价值、内在价值和工具价值。

某个目标可能有1个或多个价值,以跑步为例:

  • 有的人跑步,是为了获得自律的标签,跑步里程累计达到成百上千公里,会让他感到骄傲和满足,这是成就价值。

  • 有的人跑步,是享受跑步这件事本身,早晨的阳光或者大汗淋漓都能让人心情愉悦,这是内在价值。

  • 有的人不喜欢跑步,但一直坚持跑步,是因为想要有个好身体以便努力挣钱,这是工具价值。

不同时间同一件事,三种价值的比例会不断变化:

  • 某段时间气候特别好,你觉得跑步特别舒服,则是内在价值在起主导作用。

思考一下:

  • 某些自己想做却一直没开始做的事,是否能通过为其附能上更多的价值以驱使自己开始行动呢?

二、通知链 ( notifier call chain ) 快速入门

目录:

1
1. 什么是通知链?
2
2. 通知链的内部实现
3
3. 如何使用通知链?
阅读全文 »

哈喽,周末愉快。我是老吴,继续记录我的学习心得。

一、沉浸式学习

以学习一门语言为例:
大多数人都持有一种观念,要真正学好一门语言必须得去所学语言当地学习或生活一段时间。

而事实上,大多数人都没有这样的学习条件。

解决方法是:
自行改造环境,为自己创造沉浸式的学习环境。

例如:

  • 看新语言的电影;

  • 更改手机、电脑的语言设置;

  • 看新语言的文档和书籍;

  • 用新语言写 todo list;

  • 用新语言来学习自己需要的专业知识;

  • 翻译新语言的文档并分享;

  • 逛所学语言的论坛和网站;

  • 用新语言写代码注释 / commit message / README / issue;

对了,我作为英文的爱好者,一直想重启我的英文学习之路,后续想在公众号里记录一些英文相关的知识,请你们不要笑话我~


二、字符串函数库:Simple Dynamic Strings

1. 简介

Simple Dynamic Strings (简称 SDS) 是一个 C 语言字符串库,它增强了 C 语言字符串处理的能力。

设计 SDS 原本是为了满足设计者自身日常的 C 编程,后来又被转移到 Redis 中,在 Redis 中被广泛使用并对其进行了修改以适合于高性能操作。 现在,它又被从 Redis 中提取出来的,并 fork 为一个独立项目。

只有 1500 行不到的代码,就能做到 3.2K 个 star,牛牛牛~

优点:

  • 使用更简单;

  • 二进制安全;

  • 效率更高;

  • 与 C 字符串函数兼容;

源码链接:

1
https://github.com/antirez/sds

源码文件:

1
sds.c
2
sdsalloc.h
3
sds.h
4
testhelp.h

相关 API:

1
sds sdsnewlen(const void *init, size_t initlen) 
2
sds sdsempty(void) 
3
sds sdsnew(const char *init) 
4
sds sdsdup(const sds s) 
5
void sdsfree(sds s) 
6
void sdsupdatelen(sds s) 
7
void sdsclear(sds s) 
8
sds sdsMakeRoomFor(sds s, size_t addlen) 
9
sds sdsRemoveFreeSpace(sds s) 
10
size_t sdsAllocSize(sds s) 
11
void *sdsAllocPtr(sds s) 
12
void sdsIncrLen(sds s, ssize_t incr) 
13
sds sdsgrowzero(sds s, size_t len) 
14
sds sdscatlen(sds s, const void *t, size_t len) 
15
sds sdscat(sds s, const char *t) 
16
sds sdscatsds(sds s, const sds t) 
17
sds sdscpylen(sds s, const char *t, size_t len) 
18
sds sdscpy(sds s, const char *t) 
19
int sdsll2str(char *s, long long value) 
20
int sdsull2str(char *s, unsigned long long v) 
21
sds sdsfromlonglong(long long value) 
22
sds sdscatvprintf(sds s, const char *fmt, va_list ap) 
23
sds sdscatprintf(sds s, const char *fmt, ...) 
24
sds sdscatfmt(sds s, char const *fmt, ...) 
25
sds sdstrim(sds s, const char *cset) 
26
void sdsrange(sds s, ssize_t start, ssize_t end) 
27
void sdstolower(sds s) 
28
void sdstoupper(sds s) 
29
int sdscmp(const sds s1, const sds s2) 
30
sds *sdssplitlen(const char *s, ssize_t len, const char *sep, int seplen, int *count) 
31
void sdsfreesplitres(sds *tokens, int count) 
32
sds sdscatrepr(sds s, const char *p, size_t len) 
33
int is_hex_digit(char c) 
34
int hex_digit_to_int(char c) 
35
sds *sdssplitargs(const char *line, int *argc) 
36
sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) 
37
sds sdsjoin(char **argv, int argc, char *sep) 
38
sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen)

2. 比较常用的功能

2.1 创建字符串

sdsnew() 和 sdsfree():

1
#include <stdio.h>
2
#include "sds.h"
3
#include "sdsalloc.h"
4
5
int main(void)
6
{
7
    sds mystr = sdsnew("Hello World!");
8
    printf("%s\n", mystr);
9
    sdsfree(mystr);
10
}

运行效果:

1
$ gcc -o sdsdemo sds.c sdsdemo.c
2
$ ./sdsdemo
3
Hello World!

看到了吗?

printf 直接就可以打印 sds,这就是说 sds 本身就是 C 语言的字符串类型。

sds 的定义如下:

1
typedef char *sds;

也就是说,sds 是能兼容 libc 里字符串处理函数 (例如strcpy, strcat…)的。

当不再使用 sds 字符串时,就是是空串,也要通过 sdsfree 销毁字符串。

2.2 获取字符串长度

sdsnewlen():

1
int main(void)
2
{
3
    char buf[3];
4
    sds mystring;
5
6
    buf[0] = 'A';
7
    buf[1] = 'B';
8
    buf[2] = 'C';
9
    mystring = sdsnewlen(buf,3);
10
    printf("%s of len %d\n", mystring, (int) sdslen(mystring));
11
}

运行效果:

1
$ ./sdsdemo
2
ABC of len 3

和 strlen() 有 2 点不同

  • 运行时长固定,sds 内部有数据结构保存着字符串的长度;

  • 长度与字符串内是否有 NULL 无关;

2.3 拼接字符串

sdscat():

1
int main(void)
2
{
3
    sds s = sdsempty();
4
    s = sdscat(s, "Hello ");
5
    s = sdscat(s, "World!");
6
    printf("%s\n", s);
7
}

运行效果:

1
$ ./sdsdemo
2
Hello World!

sdscat 接受的参数是以 NULL 结尾的字符串,如果想摆脱这个限制,可以用 sdscatsds()。

sdscatsds():

1
int main(void)
2
{
3
    sds s1 = sdsnew("aaa");
4
    sds s2 = sdsnew("bbb");
5
    s1 = sdscatsds(s1,s2);
6
    sdsfree(s2);
7
    printf("%s\n", s1);
8
}

运行效果:

1
$ ./sdsdemo
2
aaabbb

2.4 扩展字符串长度

sdsgrowzero():

1
int main(void)
2
{
3
    sds s = sdsnew("Hello");
4
    s = sdsgrowzero(s,6);
5
    s[5] = '!'; /* We are sure this is safe*/
6
    printf("%s\n", s);
7
}

运行效果:

1
$ ./sdsdemo
2
Hello!

2.5 格式化字符串

sdscatprintf():

1
int main(void)
2
{
3
    sds s;
4
    int a = 10, b = 20;
5
    s = sdsnew("The sum is: ");
6
    s = sdscatprintf(s,"%d+%d = %d",a,b,a+b);
7
    printf("%s\n", s);
8
}

运行效果:

1
$ ./sdsdemo
2
The sum is: 10+20 = 30

2.6 截取字符串

sdstrim():去掉指定字符

1
int main(void)
2
{
3
    sds s = sdsnew("         my string\n\n  ");
4
    sdstrim(s," \n");
5
    printf("-%s-\n",s);
6
}

运行效果:

1
$ ./sdsdemo
2
-my string-

去掉了空格和换行符。

sdsrange():截取指定范围内的字符串

1
int main(void)
2
{
3
    sds s = sdsnew("Hello World!");
4
    sdsrange(s,1,4);
5
    printf("-%s-\n", s);
6
}

运行效果:

1
$ ./sdsdemo
2
-ello-

2.7 字符串分割 (Tokenization)

sdssplitlen() 和 sdsfreesplitres():

1
int main(void)
2
{
3
    sds *tokens;
4
    int count, j;
5
6
    sds line = sdsnew("Hello World!");
7
    tokens = sdssplitlen(line, sdslen(line)," ",1,&count);
8
9
    for (j = 0; j < count; j++)
10
        printf("%s\n", tokens[j]);
11
    sdsfreesplitres(tokens,count);
12
}

sdssplitlen() 第 3和4 个参数指定分割符为空格。

运行效果:

1
$ ./sdsdemo
2
Hello
3
World!

2.8 字符串合并 (String joining)

sdssplitlen() 和 sdsfreesplitres():

1
int main(void)
2
{
3
    char *tokens[3] = {"foo","bar","zap"};
4
    sds s = sdsjoin(tokens, 3, "|");
5
    printf("%s\n", s);
6
}

运行效果:

1
$ ./sdsdemo
2
foo|bar|zap

还有其他一些功能,用到再研究吧!

3. 简单了解一下内部实现

在 SDSD 中,使用二进制前缀(头部) 来保存字符串相关的信息,该头部存储在 SDS 返回给用户的字符串的实际指针之前:

1
+--------+-------------------------------+-----------+
2
| Header | Binary safe C alike string... | Null term |
3
+--------+-------------------------------+-----------+
4
         |
5
         `-> Pointer returned to the user.

这个 Header 在代码中用结构体来描述,该结构体定义大致如下:

1
struct sdshdr {
2
    [...]
3
    int len;
4
    char buf[];
5
};
  • len 存储的是字符串长度;

  • buf 指向紧随其后的字符串首地址;

假设你使用的字符串为 “HELLOWORLD”,为了提升效率,SDS 可能会提前分配多一些空间,所以实际的内存布局如下:

1
+------------+------------------------+-----------+---------------\
2
| len | buf | H E L L O W O R L D \n | Null term |  Free space   \
3
+------------+------------------------+-----------+---------------\
4
             |
5
             `-> Pointer returned to the user.

现在,我们来看一下 SDS 分配字符串的大致步骤:

1
sds sdsnew(const char *init)
2
    initlen = (init == NULL) ? 0 : strlen(init);
3
    sdsnewlen(init, initlen);
4
        int hdrlen = sdsHdrSize(type);      // 确定 Header 的长度
5
        sh = s_malloc(hdrlen+initlen+1);    // 分配 Header + String + 1 个字节的空间
6
7
        s = (char*)sh+hdrlen;       // 保存 C string 的地址
8
        SDS_HDR_VAR(8,s);           // 定义 struct sdshdr sh
9
        sh->len = initlen;          // 初始化 struct sdshdr sh
10
11
        if (initlen && init)        // 初始化 C string 
12
            memcpy(s, init, initlen);
13
        
14
        s[initlen] = '\0';          // 总是添加一个 NULL
15
        return s;                   // 返回 C string

其他的 SDS API 是如何实现的,就留给大家自行分析了。

4. 相关参考

-《Linux程序设计》,6,7.1 章节

-《C primer plus》,11,12 章节

-《C 和指针》,9 章节

-《Linux 系统边城》,9 章节

-《C专家编程》,7.5 章节

-《C和C++程序员面试秘笈》,4 章节


三、思考技术,也要思考人生

学习技术,更要学习如何生活

你和我各有一个苹果,如果我们交换苹果的话,我们还是只有一个苹果。但当你和我各有一个想法,我们交换想法的话,我们就都有两个想法了。

嵌入式系统 (Linux、RTOS、OpenWrt、Android) 和 开源软件 感兴趣,想和更多人互相交流学习,请关注公众号:嵌入式Hacker

觉得文章对你有价值的话,不妨点个 在看和点赞 哦。

哈喽,我是老吴,继续记录我的学习心得。

一、进步的滞后性

我们期望进步是线性:
每一个人付出一些努力后,都希望它有立竿见影的效果。

现实是:
做出努力后,结果的显现往往滞后。

只有在几个月或几年后,我们才意识到以前学习/工作的真正价值。

“失望谷地” 的出现:
人们在投入数周或数月的辛勤工作后,却没有任何看得见的效果,于是进入深感沮丧的时期。

如何改变:
1> 改变意识。功夫并没有白费,它只是蓄积起来了。直到很久以后,以前努力的全部价值才会显露出来。

2> 寻找一些可以帮助自己对抗滞后进步所带来的低落情绪的技巧。例如培养习惯的四大定律,具体的如即时奖励、习惯追踪等。


二、triggerhappy 源码分析 / 3.select() 的应用

正文目录:

1
1. thd.c / process_events() 源码分析
2
2. I/O 多路复用 ( Multiplexing ) 是为了解决什么问题
3
3. I/O 多路复用设计思路
4
4. select() 的用法
5
5. 和 poll/epoll 对比(补充知识,非重点)
6
6. triggerhappy:thd.c / process_devices() 源码分析
7
7. 相关参考

写作目的:

  • 通过阅读 triggerhappy 的源码,学习 I/O 多路复用的方法之一 select() 的使用方法。
阅读全文 »

哈喽,我是老吴,继续记录我的学习心得。

一、保持专注的几个技巧

  • 将最重要的事放在早上做。

  • 待在无干扰环境下,比如图书馆。

  • 意识到刚坐下开始投入工作前,有点负面小情绪是特别正常的现象。

  • 让“开心一刻”成为计划的一部分。

  • 拥有合情合理的日计划和周计划。


二、Linux 字符设备驱动内幕 (1)

正文目录:

1
1. 什么是字符设备驱动?
2
2. 快速体验字符设备驱动和应用程序 (超简单的 demo)
3
3. 字符设备在内核里的抽象
4
    3.1 字符设备核心代码概览
5
    3.2 对字符设备进行抽象: struct cdev
6
    3.3 对字符设备的操作进行抽象:struct file_operations
7
4. 更多值得学习的知识点
8
5. 相关参考

写作目的:

  • 探索 Linux 字符设备驱动。
阅读全文 »

哈喽,我是老吴,今天分享一点小经验。

一、个人玩家,如何判断一块开发板好坏?

  1. 了解 cpu 原厂的综合实力,谷歌一下就能看到全球各大半导体公司的排名。一般来说,无论是代码质量,文档资料,还是技术支持,外企的芯片会比国产芯片好,唯一的缺点就是价格更贵,但这对个人玩家来说是无所谓的。对于一款 cpu,一般就买一个板子。这样的话,1 个芯片的溢价最多就 100~200 块,但是对于带来的品味提升和学习难度的下降,以及后续带来的求职竞争力的提升(前提是你投入了时间去学习),这点小钱不值得一提,这笔投资性价比超高
阅读全文 »

哈喽,我是老吴,继续记录我的学习心得。

一、能力错觉

当书本(或谷歌)摆在眼前时,大脑会产生错觉,以为学习材料也同样存入了大脑,阅读毕竟比回想简单多了。

以为反复的阅读资料就是自己已经掌握知识,这就是能力错觉

解决能力错觉的方法:

  • 积极回想——让大脑提取关键概念,而非通过重复阅读被动地获取知识,这样才能高效地学习。

现在网络上盛行各种it类的视频教程,我不否认不少视频教程是高质量的,但是所有视频类资料都有一个问题:

  • 可以让人不用阅读书籍,减少思考,只要被动地听老师们讲课就能舒舒服服地获取到知识,这很容易会让某些初级的软件开发人员形成能力错觉,仿佛视频里写过的代码,解决过的 bug 都是自己已经学到的知识似的。

有效的解决办法是

  • 公开学习笔记和练习代码。公开学习笔记的目的是借助外部压力,高效回想,进而提高自己的学习标准。

  • 另外,公开写作则会给你的写作增加很多维度的外部压力,你会想如何让别人更好地理解我要表达的意思;如何传递更多价值,让别人读完有所收获;如何让更多人看到;如何让别人读得下去;如何排版让大家看得更舒服;


二、数组和指针有什么区别?

正文目录:

1
1. 用于声明时两者有重大区别
2
2. 你真的理解声明和定义吗?
3
3. 数组和指针的底层是如何访问数据的?
4
4. 哪些场景可以用指针代替数组?
5
5. 为什么C语言要把数组形参退化为指针?
6
6. 如何使用指针访问多维数组?
7
7. 相关面试题

写作目的:

  • 正确看待数组和指针。
阅读全文 »


哈喽,我是老吴,继续记录我的学习心得。

一、构建思维组块

构建组块(chunking):

  • 就是利用一些概念,组合起信息碎片,这是一种心智上的飞跃。

构建组块的方法:

  • 把注意力集中在需要组块的信息上 。思维就像章鱼触手,一旦分心就无法抓紧。

  • 把基本概念打包成组块,然后理解这个基本概念。专注和发散模式的交替思考,总能理清头绪,把握概念。

  • 获取背景信息,不仅是知道如何进行组块,还要知道何时何地如何使用该组块。

学习活动包括 “以上至下、从下至上” 两个方向:

  • 从上至下的宏观学习,和从下至上的组块活动,两个过程对熟练掌握知识都有重要作用。

应用到软件开发上:

  • 大多数编程书里是教授的都是大量的细微的知识点,我们需要先把这些非常小的知识点体系化,形成多个组块,这是从下至上的组块活动。观察现有的优秀软件 (一般是开源软件) 是如何使用组块的,这是从上至下的宏观学习。

  • 学习软件开发要多练习抽象,写代码是抽象再组合,而读代码则是分解出一个个小的组块。


二、Linux系统编程 / 分析开源软件 Triggerhappy / (2) 把握核心流程

正文目录:

1
1. 分解 thd.c / start_readers()
2
1.1 thd.c / start_readers() 的作用
3
1.2 thd.c / start_readers() 的内容
4
    1) 根据命令行参数,判断是否需要打开 socket
5
    2) 将命令行中指定的输入设备添加到设备链表中
6
    3) 根据命令行参数,决定是否成为守护进程 (daemon)
7
    4) 根据命令行参数,决定是否创建 pidfile
8
    5) 真正开始监控设备并处理事件

写作目的:

  • 通过分析开源软件 Triggerhappy,练习 Linux 系统编程。
阅读全文 »


哈喽,我是老吴,继续记录我的学习心得

一、自制力太强导致低效专注

自制力很强的人的成功之道在于,在别人都放弃的情况下仍坚持不懈。

但是这反而会让他们难以关闭专注模式,导致无法进入解决难题所必要的发散模式。

但是,对于这种人 ( 我自己典型案例 ),需要特别重视学习另外一个技巧:多点倾诉和多点倾听。将把同伴、朋友或亲人的意见放在心上。

有时我在尝试解决一个 BUG 的时候,我很容易就进入专注模式,我会认为我一直在深挖问题的根源,迟早会确定问题。但是 更有效地方式是:一段时间的专注加上小片时间的沟通

当我尝试向同事解释我所遇到的问题时,一般收获都会很大:

  1. 帮自己整理思路,如果我的同事明白了我遇到的问题,说明我的思路清晰。而且这时候偶尔我自己也会灵光一闪,就想到一个新的思路。

  2. 由于同事是旁观者,很容易找到最不可理解以及最可疑的点。

  3. 同事的水平较高,经验丰富,很可能直接看出问题出在哪里。

所以,下次遇到 BUG 时,不要自己埋头苦干啦。持续的埋头苦干,可能只是无用功。

想解决问题,吃点东西,跟同事们聊会天吧。


二、Linux系统编程 / 分析开源软件 Triggerhappy / (1) 把握整体

正文目录:

1
1. HappyTrigger 是什么?
2
    1) 概述
3
    2) 简单地试用
4
    3) 配置文件
5
    4) 使用 socket 通信以动态增加和删除输入设备
6
2. 查看 HappyTrigger 源码文件
7
    1) 查看Makefile 文件以确定编译流程
8
    2) 确定 C source 文件
9
3. 分析入口文件 thd.c
10
    1) thd.c 的作用
11
    2) thd.c 的使用者
12
    3) 分解 thd.c
13
    4) 分解 thd.c/main()

写作目的:

  • 通过分析开源软件 Triggerhappy,练习 Linux 系统编程。
阅读全文 »

哈喽,我是老吴,继续记录我的学习心得。

如何学习新事物?

人有 2 种思维模式:专注模式和发散模式,你可以把这 2 种模式想象成从手电筒里打出来的光。

专注模式下的光束更紧密,穿透力更强,径直打在一小块区域上。

而发散模式的光柱则是分散的,照亮的范围更广,但各处的光强都会降低。

如果你想要理解新事物,那最好关掉精确的专注思考模式,把开关切换到 “广角光源”,直到你锁定了一个新的、更有成效的方法。

该方法也适用于阅读源代码
现在的软件越来越大,一个开源软件动不动就有几百个文件、几万行代码,想要快速应用或者解决 bug,你还有时间对每一个函数进行源码分析吗?

我的做法是这样的:

1) 先查看源码的目录层次,确定顶层目录负责的功能;

2) 对感兴趣或者需要定制的功能,再查看该目录下的源码文件,确定每一个文件的功能;

3) 开始阅读该文件内的源码,但是我也不会直接从源码的入门函数开始阅读,我会先获取该文件里的所有定义(包括函数、变量),找出自己最关心的函数或者变量进行跟踪阅读。

这是我在阅读源码这件事上对发散模式和专注模式的一次简单应用,你学到了吗?下面我分享几个自己临时想到的获取 C 文件函数名的方法 (list function),只是提供思路,不一定足够严谨和高效,谨慎参考。

觉得有收获的小伙伴们,请帮忙转发一下下哈~

Tools / 获取 c 文件函数名的几种方法

目录:

1
1. 方法1: grep 配合正则表达式
2
2. 方法2: nm 类的调试工具
3
3. 方法3: 开源软件 ctags
4
4. 使用 list function 的方法简单分析 Linux Input 子系统
5
5. 相关参考
阅读全文 »