ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

PDP-11/83复古计算机搭建Web服务器:2.11BSD系统安装与HTTP服务实现

PDP-11/83复古计算机搭建Web服务器:2.11BSD系统安装与HTTP服务实现 1. 一台四十多年前的机器怎么就成了我的Web服务器先说说这个标题到底在讲什么。PDP-11/83 是DEC公司在1980年代中期推出的一款16位小型机属于PDP-11系列里比较晚期的型号CPU是KDJ11-B跑的是211BSD——这是2.11BSD的简称一款基于UNIX System V早期版本衍生出来的BSD分支专门为PDP-11这种内存寻址能力有限的机器做了大量裁剪和优化。而“serving this page”的意思很直白这台机器现在正儿八经地跑着一个HTTP服务你访问的那个页面就是从这台四十多年前的硬件上吐出来的。我第一次看到这个项目的时候第一反应是“这人是不是闲的”。但仔细一想这件事的价值恰恰在于它的“不划算”。现在随便一台树莓派Zero都能秒杀PDP-11的算力功耗还不到它的零头。可正是因为它不划算才逼着你去理解每一个字节是怎么从磁盘读到内存、怎么经过TCP/IP栈、怎么被一个1970年代设计的操作系统调度到网卡上的。这种理解你在现代Linux上跑一万遍Nginx也得不到。这篇文章适合谁看如果你手里正好有一台复古机器想让它“活”起来或者你对UNIX的早期实现有好奇心再或者你只是想看看在没有systemd、没有动态链接、甚至没有虚拟内存的完整支持的条件下一个网络服务是怎么跑起来的那这篇内容应该能给你不少参考。我会把整个恢复过程、211BSD的安装配置、网络栈的调试、HTTP服务的搭建以及我踩过的那些坑尽量完整地讲一遍。需要提前说明的是PDP-11/83的具体硬件配置在不同批次间有差异我手上这台是KDJ11-B CPU、4MB内存、两个RD54硬盘各159MB、一块DELQA以太网卡。如果你手上的配置不同部分参数需要相应调整但整体思路是通用的。2. 为什么选211BSD而不是其他系统2.1 PDP-11上能跑的操作系统有哪些PDP-11这个平台生命周期极长从1970年一直生产到1990年代期间DEC自己就发布过RSX-11、RSTS/E、RT-11、IAS等多个操作系统第三方还有UNIX V6、V7、2.9BSD、2.11BSD等。如果你只是想让机器跑起来选择其实不少。但如果你目标是“跑一个网络服务”筛选条件就变得很苛刻了。RT-11是实时系统没有内存保护TCP/IP栈基本没有RSX-11M虽然有网络能力但DECnet协议居多TCP/IP需要额外购买UNIX V6太老网络代码不完整V7虽然经典但对PDP-11/83的硬件支持不够完善特别是大内存和较新的磁盘控制器。2.11BSD是我认为最平衡的选择。它在2.9BSD的基础上做了大量改进支持分离的I/D空间PDP-11/83的KDJ11-B支持这个特性能利用更大的物理内存TCP/IP栈是BSD的标准实现虽然老但完整。更重要的是2.11BSD的源码和安装介质在互联网上相对容易找到社区里还有人在维护补丁。2.2 211BSD的版本选择和获取2.11BSD的最后一个官方版本是2.11BSD Patch Level 431之后社区有人继续维护到Patch Level 445左右。我建议直接用最新的patch level因为早期的版本在TCP/IP栈上有一些已知的问题比如处理窗口缩放时的bug在高延迟网络下会出问题。获取途径主要是通过模拟器社区和复古计算论坛。通常你会拿到一组磁盘镜像文件可能是.tap格式用于模拟器或者物理磁盘的dd镜像。如果你打算在真实硬件上安装需要把这些镜像写到SCSI硬盘或者DEC的RD系列硬盘上。我这边用的是SCSI转接方案因为RD54硬盘现在基本找不到完好的了。注意2.11BSD的安装介质通常是以“boot tape”形式分发的但PDP-11/83的ROM里自带的boot程序可能不支持直接从SCSI磁带启动。你需要先通过其他方式比如从RK05或RL02软盘引导一个最小的安装环境再从磁带读取完整系统。2.3 为什么不用模拟器这个问题我被问过很多次。用SIMH模拟一个PDP-11跑2.11BSD再配个虚拟网卡确实能省掉大量硬件调试的时间。但模拟器有个根本问题它把硬件的不确定性屏蔽掉了。真实机器上你可能会遇到内存校验错误、磁盘控制器超时、网卡DMA冲突这些在模拟器里永远不会出现。而正是这些问题的排查过程让你真正理解这套系统的运作方式。另外模拟器的时序和真实硬件不同。2.11BSD里有些驱动代码对时序有隐含假设在模拟器上跑得好好的到真机上可能就挂。我遇到过DELQA网卡在模拟器里工作正常但在真机上因为中断响应延迟导致丢包的情况。这种问题不摸真机是发现不了的。3. 硬件恢复从通电到能引导3.1 电源和背板检查PDP-11/83用的是BA23或BA123机箱电源是H7864或类似的型号。这些电源到现在已经三四十年了电解电容基本都干涸了。我的建议是在第一次通电之前先把电源拆出来检查所有电解电容有没有鼓包或漏液。如果有条件直接换掉所有电解电容特别是初级侧的滤波电容。背板方面Q22总线的背板一般比较可靠但插槽的簧片可能会氧化。用电子接触清洁剂喷一下然后反复插拔几次板卡确保接触良好。我遇到过因为背板接触不良导致CPU随机取指错误的情况排查了很久才发现是插槽问题。3.2 磁盘子系统的选择原装的RD54硬盘是MFM接口的容量159MB现在能找到的基本都是报废品。我的方案是用SCSI硬盘加SCSI到Q22总线的转接卡比如CMD CQD-220或者Emulex UC07。这些卡在eBay上偶尔能买到但价格被炒得很高。另一个方案是用SCSI2SD或者ZuluSCSI这样的固态存储模拟器把SCSI硬盘替换成SD卡。这个方案的好处是可靠性高、功耗低、没有机械磨损。但要注意2.11BSD的磁盘驱动可能不认识这些模拟器报告的几何参数需要在驱动里手动指定或者用工具修改分区表。我最终用的是ZuluSCSI接一张8GB的SD卡只用了前200MB左右给2.11BSD。分区方案是分区用途大小文件系统a根分区16MB2.11BSD FFSb交换分区16MBswapc整个磁盘200MB无d/usr80MB2.11BSD FFSe/home40MB2.11BSD FFSf/var20MB2.11BSD FFS这个分区方案是2.11BSD安装文档里推荐的我根据自己的需求调整了/usr的大小因为要装编译器和网络工具。3.3 内存和CPU板KDJ11-B CPU板上的PROM里包含了boot程序支持从多种设备引导。如果你要改引导设备需要进入CPU的微码控制台通过前面板的开关或者串口修改boot设备列表。具体操作是按住AUX开关然后输入设备代码。PDP-11/83的boot设备代码表在CPU板的标签上通常有印。内存方面PDP-11/83最大支持4MB物理内存但2.11BSD只能直接寻址到2MB左右剩下的需要通过分离I/D空间和bank switching来利用。我的机器装了4MB2.11BSD识别出大约3.5MB可用剩下的被内核保留做缓冲。实操心得如果你在真机上装2.11BSD第一次引导时如果看到“panic: cannot mount root”之类的错误先检查内存板有没有插紧。PDP-11的内存板金手指容易氧化用橡皮擦擦一下再插。4. 211BSD安装从磁带引导到系统可用4.1 制作安装介质如果你用的是ZuluSCSI需要把2.11BSD的安装镜像转换成ZuluSCSI能识别的格式。通常安装镜像是一个.tap文件里面包含了多个文件系统的转储。你需要用工具比如tap2scsi或者自己写脚本把它转换成磁盘镜像然后写到SD卡上。具体步骤下载2.11BSD的安装磁带镜像通常是2.11BSD_install.tap之类的文件名。用tap2scsi工具把tap文件转换成SCSI磁盘镜像。这个工具在复古计算社区有编译好的版本。把生成的镜像写到SD卡上注意偏移量要正确。ZuluSCSI默认从LBA 0开始读取所以镜像要直接dd到SD卡的开头。把SD卡插到ZuluSCSI上连接到PDP-11的SCSI总线。如果你用的是真实磁带机那就简单了直接把磁带塞进去从磁带引导就行。但磁带机本身可能也需要维护比如清洁磁头、调整张力。4.2 引导和安装过程从磁带引导后你会进入一个独立的安装环境通常是一个内存文件系统。安装脚本会引导你完成分区、创建文件系统、从磁带解压文件等步骤。关键步骤分区用disklabel命令编辑磁盘标签。注意2.11BSD的磁盘标签格式和现代BSD不同分区偏移量是以512字节为单位计算的。创建文件系统用mkfs命令参数是mkfs /dev/rd0a之类的。2.11BSD的mkfs参数和现代版本不同需要指定文件系统类型和inode数量。从磁带解压用restore命令从磁带设备读取转储文件。注意磁带设备名可能是/dev/rmt0或/dev/nrmt0取决于是否要倒带。整个安装过程大概需要30到60分钟取决于磁盘速度。ZuluSCSI比真实硬盘快很多大概20分钟就能完成。4.3 首次启动和基本配置安装完成后从硬盘引导。第一次启动时系统会问你一些基本配置问题比如时区、主机名、网络配置等。2.11BSD的启动脚本在/etc/rc和/etc/rc.local里你可以手动编辑。网络配置需要编辑/etc/hosts、/etc/netstart和/etc/rc.conf如果有的话。2.11BSD的网络配置比较原始通常是在/etc/rc.local里直接调用ifconfig和route命令。我的配置大概是这样的# /etc/rc.local 片段 ifconfig de0 192.168.1.100 netmask 255.255.255.0 route add default 192.168.1.1de0是DELQA网卡的设备名。如果你用的是其他网卡设备名可能不同比如qe0是DEQNAxp0是XNA等。注意2.11BSD的ifconfig命令不支持现代的参数格式比如inet关键字。你直接写IP地址就行不要加额外的关键字。5. 网络栈调试让TCP/IP真正跑起来5.1 DELQA网卡的驱动问题DELQA是DEC的以太网卡基于AMD LANCE芯片组。2.11BSD的de驱动支持这块卡但有一些已知的问题。最常见的是中断丢失和DMA溢出。中断丢失的表现是网卡能收到包但系统不响应ping不通。排查方法是看netstat -i的输出如果Ipkts在增加但Idrop也在增加说明中断处理有问题。DMA溢出的表现是大包传输时系统崩溃或者数据损坏。这是因为PDP-11的Q22总线DMA能力有限LANCE芯片在高速传输时可能超出总线的响应能力。解决方法在驱动里降低LANCE的DMA突发长度。2.11BSD的de驱动有一个DE_DMA_BURST之类的宏可以改成较小的值。如果中断丢失检查网卡的中断向量和CPU的中断优先级设置。PDP-11/83的中断优先级是固定的但你可以调整网卡在背板上的插槽位置来改变优先级。5.2 TCP/IP参数调优2.11BSD的TCP/IP栈是BSD 4.3的早期版本默认参数是为低速网络设计的。在现代网络上跑需要调整一些参数。关键参数在/sys/netinet/tcp.h和/sys/netinet/in.h里需要重新编译内核。我调整了以下几个TCP_MSS默认是512我改成了1024。PDP-11的内存有限MSS太大会导致分片太小会影响吞吐。TCP_SNDSPACE和TCP_RCVSPACE默认各是4096我改成了8192。现代网络的带宽延迟积比1980年代大得多缓冲区太小会导致吞吐上不去。TCP_TTL默认是60我改成了64和现代系统一致。重新编译内核的步骤cd /sys/conf cp GENERIC MYCONF # 编辑MYCONF修改参数 config MYCONF cd ../MYCONF make depend make编译过程在PDP-11/83上大概需要20到30分钟。编译完成后用cp命令把新内核复制到根目录然后重启。5.3 网络性能实测调整完参数后我用ftp和http做了简单的吞吐测试。从一台现代Linux机器上通过HTTP下载一个1MB的文件PDP-11/83的吞吐大概在80到120KB/s之间。这个速度受限于CPU的处理能力和内存带宽不是网卡的问题。ping的延迟在局域网内大概是2到5毫秒比现代机器高一个数量级但考虑到CPU主频只有十几MHz这个延迟是合理的。实操心得如果你发现网络吞吐远低于预期先检查网卡的DMA设置。DELQA有一个跳线可以设置DMA模式默认是“block mode”改成“demand mode”可能会提高稳定性但会降低吞吐。我试过两种模式block mode在轻负载下更快但重负载下容易出错。6. 搭建HTTP服务从零写一个能用的Web服务器6.1 为什么不用现成的Web服务器2.11BSD的软件仓库里有一些老版本的Web服务器比如NCSA HTTPd的早期版本或者CERN httpd。但这些软件要么依赖现代C编译器特性要么需要大量内存在PDP-11上跑起来很吃力。我决定自己写一个极简的HTTP服务器。需求很简单监听80端口接受GET请求返回静态文件支持基本的MIME类型。不需要CGI不需要SSL不需要虚拟主机。用C写大概200行代码。编译用2.11BSD自带的cc这是KR C的编译器不支持ANSI C的一些特性比如函数原型和const关键字。写代码的时候要注意。6.2 代码实现要点核心逻辑创建socket绑定80端口监听。接受连接读取请求行。解析请求行提取方法和路径。打开文件读取内容构造响应。发送响应关闭连接。关键代码片段#include stdio.h #include sys/types.h #include sys/socket.h #include netinet/in.h #include netdb.h main() { int sock, msgsock, len; struct sockaddr_in server; char buf[1024]; sock socket(AF_INET, SOCK_STREAM, 0); if (sock 0) { perror(opening stream socket); exit(1); } server.sin_family AF_INET; server.sin_addr.s_addr INADDR_ANY; server.sin_port htons(80); if (bind(sock, server, sizeof(server)) 0) { perror(binding stream socket); exit(1); } listen(sock, 5); for (;;) { msgsock accept(sock, 0, 0); if (msgsock -1) perror(accept); else { len read(msgsock, buf, sizeof(buf)); if (len 0) { buf[len] \0; handle_request(msgsock, buf); } close(msgsock); } } }handle_request函数负责解析请求和发送响应。注意2.11BSD的read和write系统调用和现代UNIX基本一致但send和recv可能不存在要用read和write代替。6.3 编译和部署编译命令cc -o httpd httpd.c如果报错说找不到socket或bind检查/usr/include/sys/socket.h和/usr/include/netinet/in.h是否存在。2.11BSD的安装可能没有包含完整的网络头文件需要从源码里复制。部署把httpd复制到/usr/local/bin。创建/var/www目录放静态文件。在/etc/rc.local里加上/usr/local/bin/httpd 让系统启动时自动运行。注意2.11BSD没有nohup命令如果你从终端启动httpd退出终端时进程会被杀掉。用放到后台也不行因为shell退出时会发送SIGHUP。解决办法是用setsid或者写一个简单的daemonize函数。我是在代码里直接调用fork和setsid让进程脱离终端。6.4 性能和安全考虑这个极简HTTP服务器没有任何安全防护。没有路径遍历检查没有请求大小限制没有超时处理。如果暴露在公网上分分钟被搞挂。我的做法是只监听局域网IP不监听公网。在路由器上做端口转发只转发特定端口到PDP-11。用inetd或者tcpd做访问控制只允许特定IP连接。如果你真的想把它暴露到公网至少加上请求行长度限制比如1024字节。路径检查拒绝包含..的请求。超时处理用alarm或者select设置超时。限制并发连接数。但这些在2.11BSD上实现起来都比较麻烦因为系统调用和库函数都比较原始。我的建议是如果只是自己玩局域网内跑跑就行了。7. 常见问题与排查技巧实录7.1 引导失败现象开机后CPU板上的LED显示错误码或者串口没有任何输出。排查思路检查电源输出电压是否正常。PDP-11/83需要5V、12V、-12V用万用表量一下。检查CPU板的PROM是否插紧。PROM芯片容易因为热胀冷缩而接触不良。检查内存板。如果有多块内存板试着只插一块排除内存故障。检查背板插槽。用已知良好的板卡替换测试。常见错误码错误码含义可能原因000001电源故障电源输出电压异常000002CPU故障CPU板或PROM问题000004内存故障内存板故障或接触不良000010总线故障背板或插槽问题7.2 网络不通现象ifconfig显示网卡已配置但ping不通。排查步骤检查网线。PDP-11/83的DELQA是AUI接口需要外接收发器。收发器故障是常见原因。检查netstat -i的输出。如果Ipkts为0说明网卡没收到包可能是收发器或网线问题。检查中断。用vmstat -i看中断计数是否在增加。如果不增加说明中断没触发可能是网卡的中断向量设置不对。检查路由。netstat -r看默认路由是否正确。检查ARP。arp -a看ARP表是否有条目。如果没有说明ARP请求没发出去或者没收到响应。我踩过的坑DELQA网卡有一个“heartbeat”跳线默认是开启的。这个功能会定期发送心跳包但在某些网络设备上会被误认为是环路导致端口被关闭。我遇到过交换机因为收到DELQA的心跳包而把端口disable的情况。解决办法是关掉heartbeat跳线。7.3 系统崩溃现象系统随机panic错误信息可能是“panic: trap”或“panic: segmentation violation”。排查思路检查内存。PDP-11的内存板容易出问题用内存测试程序跑一遍。2.11BSD的安装介质里通常有一个memtest程序。检查内核配置。如果你改过内核参数可能是参数不兼容导致的。恢复默认配置试试。检查磁盘。磁盘错误可能导致内核读取错误的代码或数据。用fsck检查文件系统。检查电源。电压不稳会导致CPU随机出错。用示波器看5V的纹波。我的经验PDP-11/83对电源质量很敏感。我的机器一开始随机崩溃换了电源里的滤波电容后就稳定了。如果你不想换电容至少加一个UPS确保输入电压稳定。7.4 HTTP服务无响应现象httpd进程在运行但浏览器连接超时。排查步骤用telnet从另一台机器连接PDP-11的80端口看是否能建立TCP连接。如果连不上说明网络或防火墙问题。如果TCP连接能建立但没响应说明httpd卡住了。用ps看进程状态如果是D状态不可中断睡眠可能是磁盘I/O问题。检查httpd的日志。我在代码里加了简单的日志输出写到/var/log/httpd.log。看日志里有没有错误信息。检查文件权限。httpd进程的用户ID可能没有权限读取/var/www下的文件。2.11BSD的权限模型和现代UNIX基本一致用chmod和chown调整。常见问题速查表现象可能原因解决方法连接被拒绝httpd没运行或端口没监听检查进程和netstat输出连接超时网络不通或防火墙阻挡检查网线和路由返回403文件权限不足chmod 644文件chmod 755目录返回404文件路径错误检查请求路径和实际文件路径返回500服务器内部错误检查httpd日志传输中断内存不足或DMA冲突减小缓冲区调整DMA设置7.5 磁盘空间不足2.11BSD的根分区只有16MB很容易满。/usr分区80MB装完编译器和网络工具后也剩不了多少。清理技巧删除/usr/src里的源码如果你不需要重新编译内核的话。删除/usr/share里的文档和man page。用compress压缩日志文件。把不常用的文件移到/home分区。我的做法我把/usr/src移到了/home/src然后在/usr里做了一个符号链接。这样根分区和/usr分区都能保持足够的空闲空间。8. 这套系统还能怎么玩8.1 跑一个简单的BBS2.11BSD上可以跑老版本的BBS软件比如Citadel或者WWIV的UNIX版本。这些软件对内存和磁盘的要求不高PDP-11/83完全能胜任。你可以通过telnet或者串口拨号接入体验1980年代的在线社区。配置BBS需要一个支持多用户的终端驱动。2.11BSD的/etc/ttys里可以配置串口终端。一个BBS软件包。Citadel的源码在复古计算社区有存档。足够的磁盘空间。BBS的消息库会不断增长建议放在/home分区。8.2 做一台DNS服务器2.11BSD自带named这是BIND的早期版本。虽然功能有限但做一台局域网DNS服务器足够了。配置/etc/named.boot和区域文件然后在/etc/rc.local里启动named。注意老版本的BIND有一些已知的安全漏洞不要暴露在公网上。局域网内用没问题。8.3 跑UUCPUUCP是UNIX到UNIX的复制协议在1980年代很流行。2.11BSD自带UUCP工具你可以用它和其他的复古UNIX系统交换文件或邮件。配置/usr/lib/uucp/L.sys和/usr/lib/uucp/L-devices然后启动uucico。UUCP可以通过串口或者TCP/IP运行。通过TCP/IP的话需要配置/etc/inetd.conf里的uucpd服务。8.4 连接现代网络PDP-11/83的TCP/IP栈虽然老但基本功能是完整的。你可以用它访问现代的HTTP服务器虽然很多网站会因为TLS要求而拒绝连接或者用ftp下载文件。如果你想让PDP-11/83访问现代网络有几个限制不支持TLS。所以HTTPS网站基本访问不了。不支持IPv6。TCP窗口缩放不支持高延迟网络下吞吐很低。DNS解析需要手动配置2.11BSD的resolv.conf格式和现代版本略有不同。我的做法是在局域网里放一台现代Linux机器做代理PDP-11/83通过代理访问外部网络。代理可以用squid或者简单的socat转发。9. 一些个人体会这台PDP-11/83从到手到能跑HTTP服务前后花了大概三个月。大部分时间不是在装系统而是在修硬件。电源换了电容内存板换了插槽网卡换了收发器硬盘换成了ZuluSCSI。每一次故障都是一次学习让你更理解这台机器的设计哲学。2.11BSD这个系统用现代的眼光看到处都是限制。内存小、磁盘慢、没有虚拟内存、没有动态链接、没有多线程。但正是这些限制逼着你去优化每一行代码去理解每一个系统调用的开销。我写那个HTTP服务器的时候为了减少一次read调用把请求行和头部一次性读进来而不是分两次读。这种优化在现代系统上毫无意义但在PDP-11上每一次系统调用都是几千个时钟周期。如果你也想搞一台复古机器跑网络服务我的建议是先从模拟器开始熟悉系统和工具链然后再上真机。真机的调试成本很高没有模拟器的基础很容易卡在硬件问题上。另外准备好足够的备件特别是电源和内存。这些机器的年龄比很多读者的年龄都大故障是常态不是例外。最后分享一个小技巧2.11BSD的/etc/rc.local里可以放任何你想在启动时运行的命令。如果你想让HTTP服务在启动时自动运行除了加httpd 之外还要确保httpd进程不会因为终端关闭而被杀掉。我的做法是在httpd代码里调用fork和setsid让它成为一个真正的daemon。如果你不想改代码可以用/usr/lib/daemon或者自己写一个简单的wrapper脚本。这个项目后续还可以这样扩展把HTTP服务改成支持CGI这样就能跑一些动态页面或者加一个简单的SMTP服务器让PDP-11/83能收发邮件再或者把系统做成一个复古计算的教学平台让更多人体验早期UNIX的魅力。
返回列表