Skip to content

How to Write A Simple Micro Kernel

  1. Intro

Hello, everybody.  You are here to learn how to write a simple micro kernel for ARMs.  Last year, I wrote a very easy micro kernel which named Totoro, spent about two months in my spare time.  I remember that i was very very excited when the kernel works.  Maybe, like Linus Torvalds when he got the Linux running around.  You can get more at Linux – a free unix-386 kernel .

I am very happy about this project. Now, let’s start!

Thread

Firstly, we need to understand the concept which named ‘thread’. A thread is combined with task status, task data. It can be paused, resumed, and stop. The main part of thread is called ‘context’. In this micro kernel, a thread, also as known TCB, is consist of register values of soc , like r0, r1, r2, … , pc, lr, sp and so on. The different part of threads is that they have their own procedure.

Context

As we have mentioned above, context is the most important part of thread.  Context keeps the states of thread in RAM, and these states will be recovery by scheduler while task switch happens.

Simple Scheduler

A scheduler rules the working logics of the system it running on.  Totoro supports robin schedule, high priority task preemption, events model programming.

Basic Data Structures

include/totoro/taskq.h

struct tasklist {
	struct ttr_tcb *tcb;
	struct tasklist *next;
};
static struct tasklist *migrate(struct ttr_tcb *tcb,
                        struct tasklist **src,
                        struct tasklist **dst)
static void insert(struct tasklist *node, struct tasklist **list);
extern ttr_err_t task_enqueue(struct ttr_tcb *tcb);
extern struct ttr_tcb *task_next(void);
extern void task_init(void);

 

Task Structure

include/totoro/taskq.h

/* definition for Task(Thread) Control Block */
struct ttr_tcb
{
	/* stack pointer */
	volatile void *sp;
	/* current state of the thread */
	uint8_t stat;
	/* priority of the thread */
	uint8_t prior;
	/* thread's stack size */
	uint32_t stack_size;
	/* thread's stack */
	void *stack;
	/* thread function */
	void (*ttr_thread)(void);
};

 

Task Priorities

include/totoro/taskq.h

#define TASK_HIGHEST_PRIORITY  ( 0 )
#define TASK_LOWEST_PRIORITY   (255)
#define TASK_DEFAULT_PRIORITY  ( 1 )

 

Task Status

include/totoro/taskq.h

#define TASK_SUSPENDED  ( 0 )
#define TASK_READY      ( 1 )
#define TASK_RUNNING    ( 2 )

 

Memag of Task Node

kernel/taskq.c

#define MALLOC_TASK_NODE(node, _tcb) \
	((node = (struct tasklist *)malloc(sizeof(struct tasklist))), \
	(!!node ? (node->tcb = _tcb, TTR_ERR_OK) : TTR_ERR_MEM))

#define DELETE_TASK_NODE(node) \
	if (node) { \
		free(node); \
		node->tcb = NULL; \
		node = NULL; \
	}

 

Context switch

arch/cortex-m0/gcc/port.S

    cpsid     i
    mrs       r0, psp
    subs      r0, #32
    stmia     r0!, {r4 - r11}
    subs      r0, #32

    ldr       r2, =ttr_running_task         
    ldr       r1, [r2]
    str       r0, [r1]

    ldr       r1, =ttr_running_task
    ldr       r2, =ttr_next_task
    ldr       r3, [r2]
    str       r3, [r1]

    ldr       r2, =ttr_next_task                
    ldr       r1, [r2]
    ldr       r0, [r1]

    ldmia     r0!, {r4 - r11}
    msr       psp, r0

    ldr       r0, =0xfffffffd
    cpsie     i
    bx        r0

 

Driver Interface

drivers/clock, drivers/gpio, drivers/serial

 

Semaphore

include/totoro/sem.h

extern int sem_signal(sem_t *sem);
extern int sem_wait(sem_t *sem, uint32_t timeout);

 

Mutex

include/totoro/simplelock.h

#define simplelock_get()
#define simplelock_put()

 

Softirq

include/totoro/softirq.h

kernel/timer.c

 

3 Test Cases

test/ab.c

test/sem.c

test/kt.c

时间

假如有一天我们能够站在宇宙的中心,环顾整个宇宙的深邃,我们便会知道自己的渺小,连地球也只是这银河里的一颗星。时间,却似是最深不可测的,什么是时间?流逝的时间,又到了哪里?宇宙是一个密闭的空间吗?它和时间的关系是什么?如果宇宙是密闭空间,那宇宙的边缘是什么?如果宇宙无边无际,是否它就是时间?人类越是对此的无知,越充满了神秘。

艺术

上世纪 92 年 12 月 21 日,我诞生在一个平凡的乡村家庭里,一个新生命的诞生,和所有平凡的孩子一样,长大。今天想谈谈,从小刻在脑海里的那一抹关于艺术的记忆。我的父亲,从我知事以来,他就一直奔波在福建与广东两省,他是村里为数不多的大卡车司机。小时候,对父亲的所有印象,可以用严肃一词加以概括。但往往,让我受益的却是潜藏在他思想里的,未曾实现的绘画艺术。我和弟弟也看过父亲画的一副画,那是一副摩托车素描,可以用生动传神来描述吧,总之,对严肃的父亲又多了一个情感,崇拜。也听一个隔壁堂哥说过,父亲以前不爱上学的时候就跑到农场里,在墙壁上画画。有一次,我自己一个人去那个地方找了找是否还有他的真迹,无奈大部分墙都已经塌掉,上面倒是有点乱涂的痕迹,那当然不是我父亲的作品。也对,堂哥说的时候我已经好几岁了,而我父亲读书的时候也才十几岁,当年的艺术早就在历史的洪流里了。仅仅是因為这个,我小时候也突然有段时间喜欢上了画画,画的不是龙就是雕,龙仿照的是我家日历上的画,雕是仿照雕牌洗衣粉包装袋上的雕,当时只想着它们是萧峰召唤的神龙,杨过身旁的神雕。

今天还想谈谈上世纪八九十年代的香港艺术家,张国荣。不知道从什么时候开始,我的乐单里,大部分歌曲来自张国荣。由此,我还发表过一系列较为极端的言论:中文歌曲的精华在粤语歌曲,粤语歌曲的精华在张国荣。这肯定要招来大批量的口水了,无奈我几乎没有粉丝,便没有收到多少口水。吐口水当然是不文明的。话说回来,为什么我会如此爱粤语,特别是张国荣的歌曲?我也曾对自己问过,我想可能有以下几点原因:1)这种对歌曲喜爱掺杂了其他情感,对张国荣个人的品性加分,颜值加分; 2)粤语歌曲带有很特殊地域风情,而老爸常年混迹与广东,对此能加分; 3)对普通话大部分垃圾歌曲的排斥导致逆向加分,对闽南歌曲不温不热导致的逆向喜爱; 4)张国荣唱工一流,用心,用情。

我很快发现,第四点是主要的因素,而第一点在第四点的基础上进行了放大,放过来又作用在了第四点。第二点有点站不住脚。偶尔第三点再给一个逆向撞击,比如听到了某普通话歌曲,难听,娇柔造作,心灵空虚,无病呻吟,小家子气等等。然而,我第一次听到张国荣的时候,并不知道他的故事,也未曾听过他的歌曲,即时是家喻户晓的倩女幽魂我都不知道是其大作。这里也可能有个加分。但是,听张国荣已经有一年多,如张国荣自己所说,他对歌曲的诠释是用情去唱,几乎每首歌曲都可以从中体会出传唱者真实而细腻的情思,因此其艺术天赋才是征服我的主要因素。从小时候的刀郎,林俊杰,到大学的五月天,似乎都没有一个真正唱到人心里的。而他,张国荣,做到了。現在我却不想拿张国荣和任何歌手去比较,他不是纯粹的歌手,他是几个世纪以来,真正用艺术去诠释人生的艺术家。他的每首歌能带出每个不一样的故事,风格似变化多端,但用心用情二词却是贯穿始终。张国荣坦言,自己在唱歌上没有花太多余力,录歌曲经常就是一次性通过,香港著名歌手许冠杰也曾为此打趣自己得录多次才得以满意,实在不及张国荣,当然是有谦虚。声线及完美的音质让张国荣在中音歌曲,甚至高音歌曲上创造了一个又一个神迹,这可能就是天赋吧。细细数来:无心睡眠,侬本多情,共同渡过,拒绝再玩,想你,沉默是金,侧面,暴风一族,似水流年,滴汗,风再起时,千千阙歌,我,玻璃之情,最爱,挪亚方舟,红,由零开始,客途秋恨,芳华绝代,你在何地,今生今世,有谁共鸣,Monica,缘分,Thank You,寂寞夜晚,红蝴蝶,枕头,这些年来 ….. 他带给世界的有太多太多的美好。而演绎生涯上,他何尝又不是创造了一个又一个经典。如今,哥哥已经离我们远去十几载,其经典的艺术仍然为我们所爱。

附一曲:无心睡眠 -张国荣

Socrates: A Privacy Plugin for Centralized IM Applications

https://io-lab.net/archives_text/socrates.txt

平整三角形:安全,分散,可讀的人名

當使用電腦時,我們喜歡用名字來指代事物。例如,這個網站被稱為“www.aaronsw.com”。你可以在瀏覽器中輸入並閱讀這些文字。我們可能需要從這些名稱中選擇三個大屬性:

  • 安全:當你在你輸入的名字,實際上得到我的網站,而不是冒名頂替者的網站
  • 分散:沒有中央當局控制所有的名字
  • 人類可讀的:這個名字是你實際上可以記住的東西,而不是一串長長的隨機性字符串

在一篇經典的論文中,我的朋友 Zooko 爭辯說,在任何時候最多可以獲得這些屬性中的兩個。

最近,DNS 傳奇人物丹·卡明斯基(Dan Kaminsky)用這個說法認為,由於電子現金幾乎與命名一樣,Zooko 的三角形也適用於它。他用這個說法認為 BitCoin 電子現金系統同时具备安全,分散,并且具有可读意義是不可能的。我有 BitCoin 的問題,但這顯然不是不可能的,所以我只是假設 Kaminsky 在某個地方出了問題。

但今晚我意識到你確實可以使用 BitCoin 來平整 Zooko 的三角形。以下是它的工作原理:

讓一個叫做滾動的文檔。滾動由一系列行組成,每行由一個元組(名稱,密鑰,隨機數)組成,這樣從一行的開始到結尾的滾動散列的前 N 位都是零。因此,要為滾動添加一行,需要做足夠的計算來發現導致哈希位為零的適當的隨機數。

要查找名稱,向每個人您所知道的具有滾動的人詢問信息,並相信最長的滾動條,然後從頭開始,以您正在查找的名稱的第一行作為關鍵字。要發布名稱,您會找到適當的隨機數,然後將新行發送給您認識的所有人。

好的,讓我們暫停一下。你如何在這樣的系統中竊取名字?首先,你需要計算一個新的隨機數,用於你想要盜取的行和每一個後續行(Gimcuan Hui 註:之所以也要計算後續行,因為 BitCoin 裏面的每次交易都被鏈接(Chain)在一起,改變一個工作記錄,後面的記錄如果沒有跟著修改就會對不上)。其次,您需要將替換捲軸交給用戶。第一點是困難的,但也許並非不可能,取決於你想要偷的名字有多少行。它需要網絡的其餘部分的 CPU 功率的大部分倍數。這對我來說似乎是一個相當強的約束,但對於 Dan 來說顯然不是。幸運的是,我們被第二個問題拯救了下來。

讓一組機器稱為網絡。每個人都記得它最後一次被信任的捲軸。當創建一條新的有效行時,它會被發送給網絡中的每個人,並將其添加到他們的捲軸中。現在,不可能竊取舊名稱,因為網絡中的機器只添加新名稱,不接受舊名稱的替換。

對於已經在網絡中的機器來說沒問題,但你如何加入?那麼,作為一個物理定律,加入一個網絡需要至少有一台機器已經在網絡中。現在,當你加入時,那台機器可以給你一個虛構的捲軸,他們偷走了所有的名字。我不認為有什麼辦法可以避免這種情況 – 如果你不認識任何一個願意告訴你正確的答案的人,你不能在空氣中找到正確的答案。即使是一個中心化的系統也至少依賴於一個誠實的根。

您可以在連接時了解多個節點並詢問每個節點的滾動情況,從而改善此問題。似乎理論上最好的情況是只要求一個節點是誠實的。這相當於信任哪個節點具有最長的滾動。但是這會讓你容易受到以下攻擊者的攻擊:a)具有足夠的 CPU 能力來製作最長的捲軸,並且 b)可以至少選擇一個初始節點。另一種方法是只信任從大多數節點列表中收到的捲軸。這使您容易受到可以控制大部分初始節點的攻擊者的影響。您大概選擇哪種折衷取決於您相信您的初始節點的多少。

發布虛假滾動等同於分割命名空間並啟動單獨的網絡。(我們可以通過要求節點簽署每個最新的捲軸並發布它們的簽名來認證網絡的成員身份來強制執行此操作,任何試圖簽署兩個相互矛盾的捲軸的節點顯然都是雙重的並且可以打折扣)。另一種描述場景(b)的方式是,為瞭加入一個網絡,你需要一個節點列表,其中至少大多數實際上是網絡中的節點。這似乎不是一個過分艱苦的要求。

而且我們實際上比這更安全一些,因為大多數人需要相當數量的 CPU 才能保持合理。 如果我們假設您從一些帶外源聽到新名稱,讓他們在攻擊者的網絡上工作,攻擊者必須有足夠的 CPU 為您可能使用的每個名稱生成行。 否則,您會意識到,您在計算機上輸入的名稱在他人的計算機上工作時會返回 404,並且開始意識到您已遭到攻擊者的攻擊。

所以你有瞭這樣一個繫統。 名字是安全的:他們可以通過任意長度的鑰匙識別,不能被盜。 它們具有可讀意義:名稱可以是任何你喜歡的字符串。 而且他們是分散的:沒有中央權威機構確定誰獲得了什麼名字,但是他們對網絡中的每個人都是可用的。

Zooko 的三角形已經平方。

Most part of this article was translated by google. Original content came from Aaron swartz’s blog which is licensed under CC BY-NC-SA.

如何写一个IM

导言

本文将介绍如何在 TOX 内核上编写一个可以进行文本交流的 IM (Instant Messaging) 软体。

TOX

TOX 是什么?它是一个分布式的 p2p 网络协议。准确来说,它提供了这样的一个网络协议的各种接口,包含安全加密、DHT 网络、洋葱路由等核心部件的实现,以及朋友请求与连接对话、群聊等模块的实现,它还包含了音视频库以供用户实现音视频交流的功能。我们要做的是调用它封装好的这些接口,来实现自己的 IM 软体。整个 TOX 采用 C 语言实现。从我这周的使用测试情况来看,TOX 内核极其稳定。这样一个优秀的去中心化内核软体,值得所有在这方面感兴趣的应用开发者在其基础上开发一个自由的 IM 软体。本文也会在最后一节列出基于此开发出来的 IM 软体。

Tox Core

首先获取 tox 核心代码:

git clone https://github.com/TokTok/c-toxcore

按照 c-toxcore 里面的指导编译,安装 c-toxcore,例如:

cd c-toxcore
git checkout v0.2.2
cmake .
make
sudo checkinstall

如果你使用的是 Debian 系统,并且采用了 checkinstall 的安装方式,那么在执行 sudo checkinstall 命令时需要在安装提示的时候设置好软件的版本,比如我们这里的 0.2.2 版本。

libsodium

在我们这个 IM 软体里面使用到了 sodium 的一些接口:

git clone git://github.com/jedisct1/libsodium.git

参考里面的安装指导进行安装。

怎么写

https://wiki.tox.chat/developers/client_examples/echo_bot

里面有比较详细的步骤,可以实现一个 echo 功能的 IM 软体。你可以在此基础上进行开发。不再赘述。

DHT Bootstrap daemon

如果你有一个自己的 VPS,那么可以在上面运行一个 DHT 节点,在前面的描述里我们知道,TOX IM 软体在启动的时候会通过 tox_bootstrap 接口接入 DHT 网络,前提是我们需要一些已知的 DHT 节点,可想而知,这样的已知节点越多,分布式的效果越好。所以我们来自己运行一个 DHT 节点:

参考 bootstrap_daemon 进行安装,针对 systemd,systeminit 等不同 init 进程的发行版本均有说明。我使用的是 systemd Debian GNU/Linux。

这里主要对配置文件做一个小小的说明,在 /etc/tox-bootstrapd.conf 的 bootstrap_nodes 配置里:

bootstrap_nodes = (
  { // Example Node 1 (IPv4)
    address = "127.0.0.1"
    port = 33445
    public_key = "728925473812C7AAC482BE7250BCCAD0B8CB9F737BF3D42ABD34459C1768F854"
  },
  { // Example Node 2 (IPv6)
    address = "::1/128"
    port = 33445
    public_key = "3E78BACF0F84235B30054B54898F56793E1DEF8BD46B1038B9D822E8460FAB67"
  },
  { // Example Node 3 (US-ASCII domain name)
    address = "example.org"
    port = 33445
    public_key = "8CD5A9BF0A6CE358BA36F7A653F99FA6B258FF756E490F52C1F98CC420F78858"
  }
)

默认的配置是以上这样的,我们需要修改成可以用的节点配置,你可以参考 tox user nodes 进行修改,如:

bootstrap_nodes = (
  { // Example Node 3 (US-ASCII domain name)
    address = "node.tox.biribiri.org"
    port = 33445
    public_key = "F404ABAA1C99A9D37D61AB54898F56793E1DEF8BD46B1038B9D822E8460FAB67"
  }
)

也就是对 address, port, public_key 三个字段进行设置,完毕之后重启这个服务(上述链接会有相应的重启方法)。当然,你也可以配置成我自己配置的节点信息:

bootstrap_nodes = (
  {
    address = "io-lab.net"
    port = 33445
    public_key = "99A6903D1B1BC59C81CAF9A330301627EAB88E39376363160FF5316910E0BB3D"
  }
)

IM Clients

TOX IM 软体在各大系统平台上均有广泛应用的客户端,有基于 ncurse 界面的 toxic,基于 qt 的 qTox,安卓上的 Antox,iOS 上的 Antidote 等等。你可以从以下链接里下载到适合自己的客户端:TOX Clients

PS: 当然,客户端这个词,对于 TOX 分布式软体来说,是不准确的,在真正的去中心化分布式网络里,大家都是对等的网络,既可以是服务者,又是使用者。

感悟

让我感到不安,愤懑,无奈的是,可能在我的朋友圈里,不会有一个人在用 TOX 即时聊天软体。是不是已经习惯了一种无所谓的生活态度,思想会飞已是一种病。现在,即使是我自己也没办法完全摆脱对微信,QQ 等垃圾 IM 软体的使用。但我相信有一天,有自由意识的人都会慢慢意识到自己的现状,我相信像 TOX 这样的自由软体才是美好世界的未来,即使等到那天,可能我们都很老了,但受益的可能会是我们自己的孩子。

参考

1,https://wiki.tox.chat/developers/client_examples/echo_bot

2,https://wiki.tox.chat/users/nodes

Sunrise: A Song of Two Humans

“God is giving you,

in the holy bonds of matrimony,

a trust.

She is young…and inexperienced.

Guide her and love her…

…keep and protect

her

from all harm.”

如何写一个微内核

导言

有时常关注我博客的朋友知道,去年我自己开发过一个很简单的微内核 Totoro,目前已经暂时停止更新。从开发到能真正运行花了一两个月的时间,当然都是我在业余时间做的啦。还记得当时那种兴奋的心情,可能跟 Linux 的最初开发者 Linus 差不多,有兴趣可以看这篇文章 Linux – a free unix-386 kernel

当然了,水平毕竟不是一个层次的。不过今天,我想把如何开发这样一个简单的内核给记录下来,一来是让有兴趣的后来者可以很快的摸到门路,二来是做一个阶段性的总结,再来就是看看自己还有没有兴致继续把它做得更好,毕竟现在漏洞百出,也不可能给别人拿去用。总的来说,出来做了三年的软件开发,没做过什么高端的有难度的东西,这样一个作品让我自己还是比较开心的!

关于单线程

从单片机(又称单板机)玩过来的同学应该知道,我们刚开始写代码的时候,几乎都是裸奔在处理器上的。写个流水灯,点亮数码管,高端点的来个串口打印。这对刚接触的同学其实就有很多东西可以学了。但是人也总不会一直停步不前,写了太多的单线程代码的菜鸟开始思考,多线程到底多了什么了呀?说到这其实我很想笑,假如你去问一问那些入门语言是 Java,Python 的同学,他们一定会说,多线程有什么了不起啊!对呀,对这些这些高级语言来说,多线程?开玩笑,随便调个接口,要几十个线程,甚至几百个,分分钟给你造好了。在这些人眼里,这个东西,没什么了不起。但对于出身即低端又比较吝啬的嵌入式的同学们来说,这个多线程词汇,却魔咒般的充满了太多的神秘感。那么多线程到底指的是什么?我们不去讨论太复杂的东西,不去讨论进程空间,虚拟内存等等高端神秘的词汇。我们只关注上下文环境。这个对于容易满足的嵌入式开发者来说,已经很知足了。时常感慨,是不是嵌入式开发做久了,人也会对自己变吝啬。

上下文环境

上下文(Context)环境,指的是这个线程运行时的一些状态,比如局部变量的值,寄存器里保存的值,比如,PC 指针指向那里,代表程序即将运行到那里,SP 指针,记录了调用信息,LR 记录了回去的路(返回地址)等等。那么要实现这个多线程我们需要做些什么?

保存上下文。我们在做线程调度的时候,将当前线程的上下文环境保存在那个线程申请的“栈”内存里(其实就是一个大数组)。然后通过一系列的底层操作,将要被调度的线程唤出来。其实也就是把之前保存的上下文从内存里读到寄存器上啦,只不过这个过程需要一些技巧,我们会在代码解析里面讲到。其实多线程就是这样的啦,那么我的这篇文章是不是该完结了?不尽然,我们要做到微内核的基本功能,那还得有调度器呀。

傻逼式调度器

我们要实现这样一个调度器。对于平级线程,也就是优先级一样的应用线程,我们会去分片(Time-sharing)执行它们。对于高优先级应用线程,我们会义无反顾的一直执行它,直到它愿意自己挂起。我觉得这是最自然的调度器,因为优先级既然高,那么也就得优先执行。当然,也是很傻逼的调度器啦。我们现在的目标是,保持一切尽量的简单。这样才有兴趣继续干下去嘛。这样约定好了以后,我们开辟了两个队列,一个用于保存处于准备态的线程,一个用于保存挂起的线程。而当前运行的进程会由一个 current 任务结构体指针来标记。所以一个任务执行完之后会有两种状态,一个是挂起,一个是再次处于准备状态。这样我们就需要一个链表接口模块。

基础数据结构

我们用单链表结构来实现这两个队列。

include/totoro/taskq.h

struct tasklist {
	struct ttr_tcb *tcb;
	struct tasklist *next;
};

实现单链表的几个接口:

static struct tasklist *migrate(struct ttr_tcb *tcb,
                        struct tasklist **src,
                        struct tasklist **dst)

这个接口做任务迁移,将挂起任务迁移到准备态或者反过来。注意到,我们采用了二级指针来管理这个单链表,这样可以省掉很多不必要的条件判断。

static void insert(struct tasklist *node, struct tasklist **list);

这个接口用于插入任务之用。

extern ttr_err_t task_enqueue(struct ttr_tcb *tcb);

这个接口用于注册任务之用。

extern struct ttr_tcb *task_next(void);

这个接口用于提取下一个要运行的任务。

extern void task_init(void);

猜猜这是干嘛的。

 

任务结构

include/totoro/taskq.h

/* definition for Task(Thread) Control Block */
struct ttr_tcb
{
	/* stack pointer */
	volatile void *sp;
	/* current state of the thread */
	uint8_t stat;
	/* priority of the thread */
	uint8_t prior;
	/* thread's stack size */
	uint32_t stack_size;
	/* thread's stack */
	void *stack;
	/* thread function */
	void (*ttr_thread)(void);
};

看到这里,聪明的同学一定能看到了,我都帮你们注释好了每个成员的作用,因此也不赘述,大家如果看了代码应该不难发现,大部分关键的地方都 加上了注释。

 

任务优先级

include/totoro/taskq.h

#define TASK_HIGHEST_PRIORITY  ( 0 )
#define TASK_LOWEST_PRIORITY   (255)
#define TASK_DEFAULT_PRIORITY  ( 1 )

我们提供给应用程序一个默认优先级,这样用户就不需要做太多纠结了。

 

任务状态

include/totoro/taskq.h

#define TASK_SUSPENDED  ( 0 )
#define TASK_READY      ( 1 )
#define TASK_RUNNING    ( 2 )

 

任务内存申请

kernel/taskq.c

#define MALLOC_TASK_NODE(node, _tcb) \
	((node = (struct tasklist *)malloc(sizeof(struct tasklist))), \
	(!!node ? (node->tcb = _tcb, TTR_ERR_OK) : TTR_ERR_MEM))

#define DELETE_TASK_NODE(node) \
	if (node) { \
		free(node); \
		node->tcb = NULL; \
		node = NULL; \
	}

对的,我们实现了两个漂亮的宏来做任务结构体内存申请和释放,史上最小的 MM。

 

线程切换

是不是很紧张,终于来到破解魔咒的关键点。COME WITH ME!

arch/cortex-m0/gcc/port.S

    cpsid     i
    mrs       r0, psp
    subs      r0, #32
    stmia     r0!, {r4 - r11}
    subs      r0, #32

    ldr       r2, =ttr_running_task         
    ldr       r1, [r2]
    str       r0, [r1]

    ldr       r1, =ttr_running_task
    ldr       r2, =ttr_next_task
    ldr       r3, [r2]
    str       r3, [r1]

    ldr       r2, =ttr_next_task                
    ldr       r1, [r2]
    ldr       r0, [r1]

    ldmia     r0!, {r4 - r11}
    msr       psp, r0

    ldr       r0, =0xfffffffd
    cpsie     i
    bx        r0

这份代码是从 GCC 分支拷贝下来的,没有一行注释,大家可以去看 ARM 分支里面的同一个文件,写全了注释,我这个人很懒,不喜欢写太多注释。

1,关全局中断,保存程序栈指针,调整栈指针,保存当前线程的寄存器值;

2,保存当前运行线程的任务指针;

3,加载即将运行线程的任务指针;

4,加载即将运行线程的上下文环境;

5,恢复程序栈指针;

6,开全局中断,返回调用处;

 

驱动接口

drivers/clock, drivers/gpio, drivers/serial

Totoro 提供了 Clock, GPIO, Serial 底层驱动的框架与实现。参见代码。

 

信号量

等等,一个内核最基本的东西是什么?很明显,它需要线程切换,肯定也要一个线程同步机制啦。我们根据 POSIX 规范的接口实现了这样一个模块。

include/totoro/sem.h

extern int sem_signal(sem_t *sem);
extern int sem_wait(sem_t *sem, uint32_t timeout);

 

互斥锁

include/totoro/simplelock.h

#define simplelock_get()
#define simplelock_put()

 

Softirq

include/totoro/softirq.h

kernel/softirq.c

当然了,为了提供 Bottom Half 机制,用于提供用户定时任务,耗时任务,中断延迟处理我们加入了软中断模块。目前是用一个高优先级的内核线程实现,用一个简单的超时机制实现用户任务之间的均衡处理。

 

3 Test Cases

test/ab.c

test/sem.c

test/sirq.c

大家看名字就知道干嘛用的。我们也不再贴代码了,要玩就自己去看了。好的,整个微内核玩下来就是这么个流程。说简单也很简单,但要一步一步自 己做起来,还是需要花时间,耐心。我们现在只注重优雅的实现,简单,漂亮。

 

感悟

终于来到了个人时间了,大家有这样的感觉吗,看自己以前写的代码,竟然感觉很美妙。特别是用 plain text 模式浏览的时候,我仿佛在查阅某 上世纪大神写出来的神作。呵呵,开个玩笑。好了,这篇文章差不多就这样了,未来会怎样,我暂时也没什么想法。

Linux – module_init

Linux 利用了程序编译链接的一个技巧,在GCC里可以通过 __attribute__ 将指定代码放入到某个特定的段(section)中,内核模块就是这样被统一管理起来的。如图中所示,指向模块初始化代码的函数指针都被放置在一个 “.initcall6.init” 的代码段里,而模块初始化代码则放置在另一个代码段 “.init.text”。这样的好处就是内核启动时加载内建(built-in)内核模块阶段可以很方便地根据首指针依次将所有模块遍历出来,这有点像 Unix 哲学中将代码复杂度转移到数据上去寻求简洁,事实证明这样干确实简洁漂亮。

结合代码看图:Fig 1. snapshot of *_module_init in kernel image

drivers/usb/serial/cp210x.c
include/usb/serial.h
   module_usb_serial_driver(serial_drivers, id_table)
init/main.c
include/asm-generic/vmlinux.lds.h
   do_initcalls
     initcall_levels
include/linux/module.h
include/linux/init.h
   module_init(x)
     __initcall(fn)
外部链接:

细谈 C 之 sizeof

ANSI C 对 sizeof 的定义 (6.5.3.4 The sizeof operator):

限制 ( Constrains )

1,sizeof 不能作用在以下类型上:

  • 函数类型
  • 未完成类型
  • 位成员

语法 ( Semantics )

2,sizeof 的结果是操作数的字节数大小,类型为无符号整型。

  • 如果数组长度可变,那么会评估它的大小;
  • 如果数组长度固定则返回一个常数;

3,具体的说:

  • 如果是 char, unsigned char signed char 则返回 1;
  • 如果数组长度固定则返回数组的字节大小;
  • 如果是结构体或者共用体则结果就是这个类型实例的总大小;

举几个例子:

32 bit 系统上:

  • sizeof (char) -> 1;
  • int a[2]; sizeof (a) -> 8;

4,sizeof 的返回结果由具体编译器实现,类型是 size_t ,见 <stddef.h>

5,用法1:分配空间

extern void *alloc(size_t);

double *dp = alloc(sizeof *dp);

6,用法2:计算数组元素个数

sizeof array / sizeof array[0]

7,用法3:可变长数组大小计算

#include <stddef.h>

size_t fsize3(int n)
{
        char b[n+3];

        return sizeof b; // 运行时 sizeof
}

8,注意:指针形式参数

当作用在形式参数上,且这个形式参数为数组或者指针类型,则 sizeof 返回的是指针的大小。

size_t func(char a[])
{
        return sizeof(a); //  return 4
}

int main(void)
{
        char a[222];

        func(a); // 4
        return 0;
}

9,作用在数值上

sizeof(20) // 4

当作一个 int 类型处理

实现

那 sizeof 是如何实现的呢?编译时?运行时?

1,编译时

编译时的 sizeof 无非就是通过简单的计算方法把对象的大小计算出来,请写出代码?

2,运行时

用法3 中我们知道,sizeof 作用在可变数组大小上需要运行时评估大小,怎么实现的呢?