在C中,很容易操作内存和硬件寄存器,因为“address”和“volatile”等概念都内置在语言中。因此,大多数操作系统都是用C语言系列编写的。例如,我可以将任意函数复制到内存中的任意位置,然后将该位置称为函数(假设硬件不会阻止我执行数据;当然,这可以在某些微控制器上工作)。
int hello_world()
{
printf("Hello, world!");
return 0;
}
int main()
{
unsigned char buf[1000];
memcpy(buf, (const void*)hello_world, sizeof buf);
int (*x)() = (int(*)())buf;
x();
}
但是,我一直在阅读某些专用Lisp机器的Open Genera操作系统。维基百科说:
Genera完全用Lisp编写;甚至所有的低级系统代码都是用Lisp编写的(设备驱动程序,垃圾收集,进程调度程序,网络堆栈等)。
我对Lisp来说是全新的,但这似乎是一件困难的事情:Common Lisp,从我所看到的,对它正在运行的硬件没有很好的抽象。 Common Lisp操作系统如何做一些基本的操作,例如编译以下简单的函数,将其机器代码表示写入内存,然后调用它?
(defun hello () (format t "Hello, World!"))
当然,Lisp可以是easily implemented in itself,但是在Sam Hughes的words中,“在某个地方,抽象用完了,机器必须执行指令。”
答案 0 :(得分:2)
Lisp机器是一台带有CPU的计算机硬件,就像现代机器一样,只有CPU有一些特殊指令可以更好地映射到LISP。它仍然是一个堆栈机器,它将其编译为CPU指令的源代码,正如现代Common Lisp实现在更普通的CPU上所做的那样。
在Lisp machines wikipedia page中,您可以看到函数如何编译:
(defun example-count (predicate list)
(let ((count 0))
(dolist (i list count)
(when (funcall predicate i)
(incf count)))))
(disassemble (compile #'example-count))
0 ENTRY: 2 REQUIRED, 0 OPTIONAL ;Creating PREDICATE and LIST
2 PUSH 0 ;Creating COUNT
3 PUSH FP|3 ;LIST
4 PUSH NIL ;Creating I
5 BRANCH 15
6 SET-TO-CDR-PUSH-CAR FP|5
7 SET-SP-TO-ADDRESS-SAVE-TOS SP|-1
10 START-CALL FP|2 ;PREDICATE
11 PUSH FP|6 ;I
12 FINISH-CALL-1-VALUE
13 BRANCH-FALSE 15
14 INCREMENT FP|4 ;COUNT
15 ENDP FP|5
16 BRANCH-FALSE 6
17 SET-SP-TO-ADDRESS SP|-2
20 RETURN-SINGLE-STACK
然后将其存储在某个内存位置,当运行此功能时,它只是跳转或调用它。与任何汇编代码一样,CPU被指示在运行此代码时继续运行其他代码,它可能是Lisp主循环本身(REPL)。
使用SBCL编译的相同代码:
; Size: 203 bytes
; 02CB9181: 48C745E800000000 MOV QWORD PTR [RBP-24], 0 ; no-arg-parsing entry point
; 189: 488B4DF0 MOV RCX, [RBP-16]
; 18D: 48894DE0 MOV [RBP-32], RCX
; 191: 660F1F840000000000 NOP
; 19A: 660F1F440000 NOP
; 1A0: L0: 488B4DE0 MOV RCX, [RBP-32]
; 1A4: 8D41F9 LEA EAX, [RCX-7]
; 1A7: A80F TEST AL, 15
; 1A9: 0F8598000000 JNE L2
; 1AF: 4881F917001020 CMP RCX, 537919511
; 1B6: 750A JNE L1
; 1B8: 488B55E8 MOV RDX, [RBP-24]
; 1BC: 488BE5 MOV RSP, RBP
; 1BF: F8 CLC
; 1C0: 5D POP RBP
; 1C1: C3 RET
; 1C2: L1: 488B45E0 MOV RAX, [RBP-32]
; 1C6: 488B40F9 MOV RAX, [RAX-7]
; 1CA: 488945D8 MOV [RBP-40], RAX
; 1CE: 488B45E0 MOV RAX, [RBP-32]
; 1D2: 488B4801 MOV RCX, [RAX+1]
; 1D6: 48894DE0 MOV [RBP-32], RCX
; 1DA: 488B55F8 MOV RDX, [RBP-8]
; 1DE: 4883EC18 SUB RSP, 24
; 1E2: 48896C2408 MOV [RSP+8], RBP
; 1E7: 488D6C2408 LEA RBP, [RSP+8]
; 1EC: B902000000 MOV ECX, 2
; 1F1: FF1425B80F1020 CALL QWORD PTR [#x20100FB8] ; %COERCE-CALLABLE-TO-FUN
; 1F8: 488BC2 MOV RAX, RDX
; 1FB: 488D5C24F0 LEA RBX, [RSP-16]
; 200: 4883EC18 SUB RSP, 24
; 204: 488B55D8 MOV RDX, [RBP-40]
; 208: B902000000 MOV ECX, 2
; 20D: 48892B MOV [RBX], RBP
; 210: 488BEB MOV RBP, RBX
; 213: FF50FD CALL QWORD PTR [RAX-3]
; 216: 480F42E3 CMOVB RSP, RBX
; 21A: 4881FA17001020 CMP RDX, 537919511
; 221: 0F8479FFFFFF JEQ L0
; 227: 488B55E8 MOV RDX, [RBP-24]
; 22B: BF02000000 MOV EDI, 2
; 230: 41BBF0010020 MOV R11D, 536871408 ; GENERIC-+
; 236: 41FFD3 CALL R11
; 239: 488955E8 MOV [RBP-24], RDX
; 23D: E95EFFFFFF JMP L0
; 242: CC0A BREAK 10 ; error trap
; 244: 02 BYTE #X02
; 245: 19 BYTE #X19 ; INVALID-ARG-COUNT-ERROR
; 246: 9A BYTE #X9A ; RCX
; 247: L2: CC0A BREAK 10 ; error trap
; 249: 02 BYTE #X02
; 24A: 02 BYTE #X02 ; OBJECT-NOT-LIST-ERROR
; 24B: 9B BYTE #X9B ; RCX
NIL
说明的说明并不多。运行此函数时,它是获取控制权的机器代码,它将控制权交还给系统,因为返回地址可能是REPL或下一条指令,就像编译的C一样。
关于lisps的一个特别之处在于需要处理词法闭包。在C中,当调用完成时,变量不再存在,但是在Lisps中,它可能返回或存储一个稍后使用这些变量并且不再在范围内的函数。这意味着变量需要处理几乎与编译代码中的解释代码一样低效,特别是对于没有做太多优化的旧编译器。
C编译器是否也进行了翻译,或者编译C的原因是什么?英特尔x86处理器不支持过程调用中的参数。它由C编译器模拟。调用者在堆栈上设置值,并且它有一个清理,然后它会撤消它。循环结构如for和while不存在。只有分支/ jmp。是的,在C中你可以更好地了解底层硬件,但它实际上与机器代码不同。它只会泄漏更多。
作为操作系统的Lisp实现可以具有诸如低级汇编指令之类的功能作为lisp操作码。然后编译将所有内容转换为低级别的lisp,然后从那些到machince字节的1:1。
具有c库和c编译器的操作系统一起完成同样的事情。它运行转换为机器代码,然后可以自己运行代码。这就是Lisp系统的工作方式,所以你唯一需要的是硬件API,它可以像内存映射I / O一样低。
答案 1 :(得分:1)
Lisp Machines有一些低级内部函数,允许它们直接访问内存和硬件寄存器。这些用于操作系统的内部。
答案 2 :(得分:1)
即使没有抽象,lisp也可以发出汇编程序。参见
但它也可以用来创建一个超薄但强大的机器代码抽象。见Henry Bakers的Comfy Compiler
最后检查SBCL VOP(example),它们允许您控制汇编代码。使用虚拟寄存器的Altough,因为这发生在寄存器分配之前。
你可能会发现this帖子很有趣,因为它涉及如何从SBCL发出汇编。
顺便说一下,即使你可以在lisp中编写驱动程序等,不必要地重复工作也不是一个好主意,所以即使是Lisp中的Lisp实现,比如SBCL,也有一些C部分允许与操作系统连接。这些C头文件以及C源文件和汇编文件是 然后使用(图2)生成sbcl可执行文件本身。该 可执行文件尚无用;而它提供了一个接口 操作系统服务和垃圾收集器
中的seccion 3.2
我没有查看Mezzano如何运作,随时可以深入研究。