在C语言中,由于诸如“地址”和“易失性”之类的概念已内置于该语言中,因此很容易操作存储器和硬件寄存器。因此,大多数操作系统都是使用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();
}
Run Code Online (Sandbox Code Playgroud)
但是,我一直在阅读有关某些专用Lisp机器的Open Genera操作系统的信息。维基百科说:
属完全用Lisp编写;甚至所有低级系统代码都用Lisp编写(设备驱动程序,垃圾回收,进程调度程序,网络堆栈等)。
我对Lisp完全陌生,但这似乎是一件困难的事情:从我所见,Common Lisp对运行的硬件没有很好的抽象。Common Lisp操作系统将如何做一些基本的事情,例如编译以下琐碎的函数,将其机器代码表示形式写入内存,然后调用它?
(defun hello () (format t "Hello, World!"))
Run Code Online (Sandbox Code Playgroud)
当然,Lisp 本身可以很容易地实现,但是用Sam Hughes 的话说:“在某个地方,抽象用完了,机器必须执行一条指令。”
Lisp 机器是一个带有 CPU 的计算机硬件,就像今天的现代机器一样,只是 CPU 有一些可以更好地映射到 LISP 的特殊指令。它仍然是一个堆栈机,并将其源代码编译为 CPU 指令,就像现代 Common Lisp 实现在更通用的 CPU 上所做的那样。
在Lisp 机器维基百科页面中,您可以看到函数是如何编译的:
(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
Run Code Online (Sandbox Code Playgroud)
然后将其存储在某个内存位置,当运行此函数时,它只是跳转或调用此函数。与任何汇编代码一样,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
Run Code Online (Sandbox Code Playgroud)
指令并不那么少。当运行这个函数时,它是获得控制的机器代码,并将控制权交还给系统,因为返回地址可能是 REPL 或下一条指令,就像编译的 C 语言一样。
一般来说,Lisp 的一个特殊之处是需要处理词法闭包。在 C 中,当调用完成时,变量不再存在,但在 Lisp 中,它可能会返回或存储稍后使用这些变量的函数,并且该函数不再在范围内。这意味着变量的处理效率几乎与编译代码中的解释代码一样低效,尤其是对于没有进行太多优化的旧编译器。
C 编译器是否也能进行翻译,否则 C 语言编程比汇编语言编程的原因是什么?Intel x86 处理器不支持过程调用中的参数。它由 C 编译器模拟。调用者在堆栈上设置值,然后进行清理,然后将其撤消。诸如 for 和 while 之类的循环结构不存在。仅分支/jmp。是的,在 C 语言中你可以对底层硬件有更多的感觉,但它确实与机器代码不同。它只会泄漏更多。
作为操作系统的 Lisp 实现可以具有诸如作为 Lisp 操作码的低级汇编指令等功能。然后编译会将所有内容转换为低级 lisp,然后这些字节与机器字节的比例为 1:1。
具有 ac 库和 ac 编译器的操作系统可以完成同样的事情。它运行机器代码的翻译,然后可以运行代码本身。这也是 Lisp 系统的工作方式,因此您唯一需要的是硬件 API,可以是低级别的内存映射 I/O 等。
| 归档时间: |
|
| 查看次数: |
1143 次 |
| 最近记录: |