如何在Lisp中与计算机硬件进行交互?

时间:2015-07-28 19:02:59

标签: common-lisp

在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中,“在某个地方,抽象用完了,机器必须执行指令。”

3 个答案:

答案 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可执行文件本身。该   可执行文件尚无用;而它提供了一个接口   操作系统服务和垃圾收集器

     

取自SBCL: a Sanely-Bootstrappable Common Lisp

中的seccion 3.2

我没有查看Mezzano如何运作,随时可以深入研究。