Random Posts
Tags
Categories
Recent Comments
- 小肥 on GDB 从裸奔到穿戴整齐
- flandre on 异步事件模型的 Self-pipe trick
- inv on 异步事件模型的 Self-pipe trick
- skywind on 异步事件模型的 Self-pipe trick
- skywind on 异步事件模型的 Self-pipe trick
Links
Meta
Tag Archives: 系统开发
库代码中是否应该检查 malloc 的返回值?
现在网上有很多似是而非的观点,比如 malloc 失败不用处理,直接退出,这样的经验看似很聪明,实际却很局限,比如: 1)嵌入式设备:不是所有设备都能有一个强大的全功能的操作系统,也不是所有设备都能有虚拟内存功能。 2)长时间运行的程序,内存虽然够,但由于碎片,会导致没有连续足够大的线性地址进而分配失败,32 位程序特别明显。 3)管理员对某类进程设置过内存限制,比如某类要起很多个的进程,那么设置一下内存限制是一个很正常的操作。 4)程序通过容器运行,事先给定了最大内存用量。 5)操作系统 overcommit 选项被管理员关闭。 所以说 malloc 失败不用检测,大多数是明确知道自己运行环境的某一类程序,比如上层业务,比如 CRUD,比如你就是做个增删改查,知道自己运行于一台标准的 linux 服务器,那么确实无需多处理,崩了也就崩了;或者你的程序严重依赖 malloc 三行代码一次分配,那么即使恢复出来估计你也很难往下走,不如乘早崩溃,免得祸害他人。 上面这些情况确实可以简单粗暴处理,但如果你开发一些基础库,你没办法决定自己是会运行在一台标准服务器上还是某个小设备里,那么上面的经验就完全失效了。 比如你开发一个类似 libjpeg 的图象编解码库,你没法假定运行环境,那么 malloc 失败时,你是该自作主张直接 assert 强退掉呢?还是该先把分配了一半的各种资源释放干净,然后向上层返回错误码,由上层决定怎么处理比较好呢?比如上层可选择:1)报错退出;2)释放部分缓存后再运行;3)降级运行,比如图象编码使用 low profile 再运行一遍。 你作为图象编码库需要经常分配一些比较大块的内存,你这里失败了,不代表上层无法继续分配内存处理后续任务对不对?上层内存里有很多图片 cache 用于加速图片显示,你这里失败了,上层感知到,直接从 cache 里回收一波,内存就又有了,对不对?图象视频编码根据资源消耗高低都有 high profile, low profile 的运行模式,你 … Continue reading
C语言如何编译出一个不需要操作系统的程序
来个更短的,没有其他乱七八糟的东西,只有一个简短的 C文件,不需要 linux 环境: miniboot.c asm(“.long 0x1badb002, 0, (-(0x1badb002 + 0))”); unsigned char *videobuf = (unsigned char*)0xb8000; const char *str = “Hello, World !! “; int start_entry(void) { int i; for (i = 0; str[i]; i++) { videobuf[i * 2 … Continue reading
如何实现一个真正高性能的spin_lock?
应用层用spinlock的最大问题是不能跟kernel一样的关中断(cli/sti),假设并发稍微多点,线程1在lock之后unlock之前发生了时钟中断,一段时间后才会被切回来调用unlock,那么这段时间中另一个调用lock的线程不就得空跑while了?这才是最浪费cpu时间的地方。所以不能关中断就只能sleep了,怎么着都存在巨大的冲突代价。 尤其是多核的时候,假设 Kernel 中任务1跑在 cpu1上,任务 2跑在 cpu2上,任务1进入lock之前就把中断关闭了,不会被切走,调用unlock的时候,不会花费多少时间,cpu2上的任务2在那循环也只会空跑几个指令周期。 看看 Kernel 的 spinlock: #define _spin_lock_irq(lock) \ do { \ local_irq_disable(); \ preempt_disable(); \ _raw_spin_lock(lock); \ __acquire(lock); \ } while (0) 看到里面的 local_irq_disable() 了么?实现如下: #define local_irq_disable() \ __asm__ __volatile__(“cli”: : :”memory”) 倘若不关闭中断,任务1在进入临界区的时候被切换走了,50ms以后才能被切换回来,即使原来临界区的代码只需要0.001ms就跑完了,可cpu2上的任务2还会在while那里干耗50ms,所以不能禁止中断的话只能用 sleep来避免空跑while浪费性能。 … Continue reading
游戏机模拟器的具体原理是什么?
“游戏机模拟器” 注重的是 “严格模拟硬件”,要精确,可以对照 MAME代码,所有问题都能在里面找到对应答案: 第一:模拟 CPU MAME里实现了各种 68000, z80,mips, sparc, arm,pic16c5x,nec, alpha,等 100 多款你见过的或者没见过的主从协处理器的模拟,虽然都是 switch case opcode,但是不像 lua虚拟机。MAME的 CPU模拟重点在 “精确实现硬件”,除了指令集实现外,还有各种软硬终端/trap/异常处理/IO实现。举个简单例子,一个游戏主机需要 4MHz 的 z80芯片,你就得给我真的按照 4Mhz来跑,每条指令计算周期,不能多也不能少,你要把 4Mhz跑成 8Mhz,游戏玩起来节奏就不一样了。比如以前老游戏机上敌人一多,就会慢下来,你实现模拟器,也得把这种慢下来给实现了。另外很多街机是双处理器,比如一块 68000 + z80,你不能复原老主机的运行速度,一些写的粗糙的游戏 ROM可能会出错。 模拟 CPU重点是 “精细”,比如浮点数误差最好一致,比如中断优先级你得模拟出来,模拟器由于按照 interval 来运行,更容易产生同时多个硬件中断被触发,比如 “手柄按键” ,多核通信之类各种东西加在一起,某个核满负荷运行的情况下,优先级低的可能永远得不到处理,弄错了可能游戏就没法玩了。 第二:模拟总线 总线也有好多规格需要实现,不同基板的总线链接不同cpu 和外设的方式都不一样,还是需要 … Continue reading
做编译器或操作系统哪个更有趣味?
同范畴类似的东西中,虚拟机开发比操作系统和编译器都有意思: 操作系统能玩的好玩的不多,做完进程管理和内存管理,其他就是脏活累活了,要得到一个成型可用的东西,没有一个团队弄不了,个人玩不转。以前调侃过这个话题: 关于LMOS自主操作系统的发展,大家有什么建议? – 韦易笑的回答 而编译器方面,优化和代码生成有llvm,用不着自己开发,其他部分基本上是一个固定套路,照着文档照着书,按固定套路来即可。 以 Lua为例,最精巧的部分就是 Lua虚拟机的实现,整个 Lua-5.3 代码中,编译部分只占2000行,剩下两万行全在实现虚拟机。大家津津乐道的各种 lua 奇技淫巧全都在它的虚拟机实现部分中。 Ruby更是如此,整个ruby代码除去库实现外,基本在实现ruby的虚拟机,编译器部分作者都懒得怎么写,直接一个的 .y文件搞定,精力都在ruby虚拟机实现上。 虚拟机难是难在开头的设计,怎样最精巧,怎样没有逻辑漏洞,别写着写着发现前后逻辑矛盾了,设计好以后就要开始架构了,先从最简单的 switch case opcode实现起来,很快你就能得到反馈看到自己的工作成果,接下来给虚拟机实现符号表,object,实现 gc,实现各种容器,优化性能,在内存中翻译成本地指令码,然后实现多线程,再给你的虚拟机实现一门汇编语言,每走一步都很有意思。且虚拟机相关的技术目前还在日新月异的发展着,光一个gc,每年都能看到很多新方法出现,大有需要继续改进迭代的地方。 实现虚拟机需要覆盖很多知识面,虚拟机设计的好,还可以嫁接很多其他语言,让这些新语言来这个虚拟机上运行。 大家夸 v8 都是说它虚拟机运行速度快,内存少,没人说 v8 的编译部分如何如何,以至于很多新语言都以v8虚拟机为运行环境。 同样,每年都有很多人尝试重新实现 lua vm,引入更灵巧的机制或者使用更新的 JIT方法。来pk官方runtime, 以及 luajit,好多人或多或少在某些方面都能并发出很多奇思妙想。 相反,从来没人碰 lua 的编译部分,为啥啊?就那样了,还碰它干嘛,没意思了啊。
如何设计一个内存分配器?
通常工程里不推荐自己写内存分配器,因为你费力写一个出来99%可能性没有内置的好,且内存出bug难调试 不过看书之余,你也可以动手自己试试,当个玩具写写玩玩: 1. 实现教科书上的内存分配器: 做一个链表指向空闲内存,分配就是取出一块来,改写链表,返回,释放就是放回到链表里面,并做好归并。注意做好标记和保护,避免二次释放,还可以花点力气在如何查找最适合大小的内存快的搜索上,减少内存碎片,有空你了还可以把链表换成伙伴算法,写着玩嘛。 2. 实现固定内存分配器: 即实现一个 FreeList,每个 FreeList 用于分配固定大小的内存块,比如用于分配 32字节对象的固定内存分配器,之类的。每个固定内存分配器里面有两个链表,OpenList 用于存储未分配的空闲对象,CloseList用于存储已分配的内存对象,那么所谓的分配就是从 OpenList 中取出一个对象放到 CloseList 里并且返回给用户,释放又是从 CloseList 移回到 OpenList。分配时如果不够,那么就需要增长 OpenList:申请一个大一点的内存块,切割成比如 64 个相同大小的对象添加到 OpenList中。这个固定内存分配器回收的时候,统一把先前向系统申请的内存块全部还给系统。 3. 实现 FreeList 池: 在你实现了 FreeList的基础上,按照不同对象大小(8字节,16字节,32,64,128,256,512,1K。。。64K),构造十多个固定内存分配器,分配内存时根据内存大小查表,决定到底由哪个分配器负责,分配后要在头部的 header 处(ptr[-sizeof(char*)]处)写上 cookie,表示又哪个分配器分配的,这样释放时候你才能正确归还。如果大于64K,则直接用系统的 malloc作为分配,如此以浪费内存为代价你得到了一个分配时间近似O(1)的内存分配器,差不多实现了一个 memcached 的 slab 内存管理器了,但是先别得意。此 slab 非彼 … Continue reading