It's relatively easy to bring up a V6 PDP-11 Unix under the Ersatz-11 PDP-11 emulator. This page (and ancillaries) tell you how to do it, and provide a number of useful tools to go with V6 Unix. (I ran into a number of pitfalls on the way to getting V6 running, using RK05 disk images from the TUHS archive, but this short writeup will tell you how to avoid them all.)
If all you want to do is bring it up so you can say you've done it, you only need this page, and read the first three sections or so. The rest of the page, along with a companion page, Improving V6 Unix, provides what may be useful information to people who want to do more. The latter covers a range of topics, including how to get the Standard I/O Library into V6, and the 'tar' command (both of which require system mods).
Much of this (e.g. how to build a new copy of the system after adding a device) may be something people with early Unix experience already know - if so, just skip it, it's there for novices, not you! :-)
Note: I have only done all this on a Windoze box, but the Ersatz-11 emulator has a Linux version available, and this should all work equally well there.
Getting the emulator, and setting it up, is extremely easy. Simply go to the demo download page, download the appropriate version of the self-extracting archive, and run it; it will unpack itself and it's ready to go.
The documentation is fairly comprehensive, but you don't really need to read it; this page tell you everything you need to know (to begin with, at least). There is also a configuration file you may eventually want to set up, but you don't need it to start with either.
To start the emulator, simply click on the E11.EXE icon. It will pop up two windows; the first (empty) one I have not figured out any use for, and you can kill it (it won't kill the emulator). The second is the PDP-11 console, and is used for both giving commands to the emulator, and as the PDP-11 console device.
One you start the PDP-11, type-in on it goes to the 11; it you need to get back to the emulator for any reason, type "Shift-Enter" and you're at an emulator prompt. To get back to the PDP-11, just hit a plain "Enter".
The difference between the two is that in the Wellsch versions i) a Western Electric rights notice has been added to the Unix bootable images (which prints on booting); ii) the RK images in the Research version don't have an RK bootstrap in block 0, and thus won't boot directly.
However, you probably don't want to use either (although you can), because those disk images are all only 4000 blocks long (to leave space for a 872-block swap area at the end of the disk), not the 4872 blocks of a complete RK05 pack. This has important consequences when running on Ersatz-11.
Ersatz-11 emulates disk packs with ordinary Windows files of the appropriate size. E.g. create a file 4872 blocks long, and you can 'mount' that file as an RK05 pack. On Ersatz-11 (at least, on E11 V7.0), to 'play nice' with packs which have bad block tables at the end of the pack which sometimes aren't included in a copy of the pack, reads to parts of the disk which aren't in the file which emulates the pack return all 0's, not an error; writes to that area of the disk appear to the PDP-11 to succeed, but don't actually do anything.
So if you start Unix under Ersatz-11 on one of the 4000-block pack images, it will eventually try to swap a process out - to the part of the disk that doesn't exist in those 4,000 block disk images, and later read it back in and get all zeros - and promptly crash.
So you need to extend those pack image files to the full 4872 blocks before
you can use them. I wrote some tools to do that
(below),
but to save you the trouble there is an extended version of the root pack
(the only one that's critical to have full length, since the initial RK-based
Unix swaps to the root RK disk) from the Wellsch set
here
which you can download.
Alternatively, there is an RK pack image there with a driver for the
emulator's very useful "DOS device" (see
below)
already added; you might prefer to just retrieve this, to the having to do
all the (painful) work to add the DOS device yourself the first time (see
below). You can download that
here.
You will probably also want the source pack (called "usr" in the Wellsch
folder), and the documentation pack may also be useful. You can either put
the image(s) in the same directory as E11, or you can put it/them elsewhere
(see below for how to proceed if you do).
First, tell the emulator what kind of PDP-11 you have. You can boot the
distributed V6 Unix images with an 11/40, in which case say to the emulator
(you don't have to type this, copy-and-paste works fine with Ersatz-11 - just
right-click to paste):
Now set the switch register for single-user boot (this is not strictly
necessary, but it's a good habit to get into):
Next, mount the RK pack image you just downloaded on drive zero:
If you put your disk image somewhere other than in the same directory
as Ersatz-11, just give the complete path name, e.g:
The first think you'll probably notice is that all typing is in upper-case,
which can be trying. Say "stty -lcase" to suppress this.
Note that on V6 Unix, the 'change directory' command is 'chdir', not 'cd'. To
fix this, you'll have to tweak the shell and recompile it (which is best done
once you have the DOS device installed, see
below,
to avoid having to do this with the line editor, 'ed'), but for the moment,
best to just live with it. When you get to it, a modified version which has
the short alternative is
here.
Unix V6 as distributed uses '#' for the character erase, and '@' for the line
kill. DELETE is the interrupt character. The latter is quite hard to change
(you'll need to edit a file and then re-build the operating system, see the
Improving V6 Unix
page for more), but you can fix the former by saying "stty erase X" and "stty
kill Y" (where X and Y are your choice of characters - e.g. ^U for line kill;
give the actual character, not the printing equivalent, i.e. "^U").
Setting your erase character to '^H' (backspace) has the added advantage
that erased characters are overwritten, making it easier to be sure of
what you've actually typed into Unix.
Unix as distributed includes almost no special files (in /dev). In
particular, there are no special files for any disks. So one of the first
things to do is create them (this has already been done on the DOS Unix disk
image):
To shut down Unix, type "sync", and then immediately go to the emulator
command level, and type HALT. With the PDP-11 halted, you can then type
"exit" or "quit" to terminate the emulator.
Note: If you're running the DOS Unix, a lot of this has already been
done - and in particular, you have hand-edited copies of l.s and c.c which
have the DOS device added. (I was too lazy to modify mkconf.c to add that
device. So shoot me! :-) So, if you run mkconf, if will overwrite
those versions. So if you do any of this, you need to carefully save them
away somewhere first! You'll then need to hand-edit the DOS device info into
any new l.s/c.c files you produce (or edit the new stuff in them back to the
hand-edited l.s/c.c which include the DOS device.)
Then run 'mkconf' to produce the appropriate c.c and l.s files. (The document
Setting Up Unix - Sixth Edition
has instructions on how to use 'mkconf', but note that to end the input, and
tell it to produce the files, a line with "done" on it is the last line of
input.)
Note that for no reason that I can understand, mkconf doesn't support
the 'my teletype' indirect device, /dev/tty. If you want that, you'll have to
add it manually; in c.c, use 'ed' to add a line like this to 'cdevsw':
Once you have l.s and c.c all set, compile/assemble them:
Booting Unix
Now that you have Ersatz-11, and the root pack, you're ready to go.
set cpu 40 eis
(The "eis" is important because the basic 11/40 does not include the MUL
instruction, which Unix uses everywhere - including in the bootstrap.
Leave that out, and the bootstrap will croak - as I found out the hard way!)
set switch 173030
Next, tell the emulator it doesn't need to run in a tight loop:
set idle delay=10
(Again, not strictly necessary, but why heat up your CPU chip when you don't
need to?)
mount dk0: UnixRoot. /rk05
(Note that you can in fact have disk image files under Ersatz-11 which
don't end in ".DSK" - just make sure to have that terminal '.' there.)
mount dk0: C:\Unix\UnixRoot. /rk05
If you're trying to boot the DOS Unix, you need to configure the DOS
device:
mount do:
Now start the bootstrap:
boot dk0:
and you should get the usual '@' prompt from the V6 bootstrap. Type the file
to load ("rkunix" in this case, or "dosunix" if you want the one with the DOS
device and have that pack), and hit 'Enter', and your Unix should boot in
single-user mode.
First things to do on Unix
(Note: Up until this point, all typein has been directed to the emulator.
You're now talking to Unix, and commands below are Unix commands. If you need
to give the emulator a command, use Shift-Return/Return to get back to the
emulator, and then return to Unix.)
/etc/mknod /dev/rk0 b 0 0
/etc/mknod /dev/rrk0 c 9 0
You can now say:
icheck /dev/rrk0
dcheck /dev/rrk0
to verify that your root pack is OK.
More disks, more devices, etc
Here are some details on how to do various other things, once you have your
Unix running.
Mounting other disks
To mount the source/documentation disk packs, you will first need to tell the
emulator (after switching to its command level :-) to mount the pack, e.g:
mount dk2: C:\Unix\UnixSrc. /rk05
You will then need to tell Unix to create the appropriate special files for
the disk device, and also the mount point. (Hint: make the mount point a
file, not a directory, then commands like 'cpall foo* /mnt' will give an
error message if the pack is not mounted.) Also, it's wise to always run
'icheck' on a disk pack:
icheck /dev/rrk2
before mounting it, to make sure there's no damage to the file-system. You
can then tell Unix to mount the pack, e.g.:
mount /dev/rk2 /src
If you're doing things on the emulated system, it's also wise to either start
multi-user operation, which will automatically (depending on the contents of
/etc/rc) start the automatic disk synchronizer (/etc/update), or start it
manually in the background (i.e. "/etc/update &"). This will reduce the
chances of disk damage if the system crashes unexpectedly (although panic()
does a sync() internally, so if you see a panic message you should be OK).
Adding devices, etc
The Unixes in the basic pack have only a single type of disk driver (I think;
I know that 'rkunix' has only an RK driver), although they support up to 8
drives of each type, and a single tty. If you want to change any of that (or
anything else about the kernel), you will need to go to /usr/sys/conf, and
compile 'mkconf':
cc -s -n -O mkconf.c
mv a.out mkconf
(No, there is no "-o" option in this version of "cc"!)
&syopen, &nulldev, &syread, &sywrite, &sysgtty, /* tty = MM */
(where MM is the correct major device number, so you don't have to count the
entries in c.c every time you need to make a new device :-). Next, after you
build the new system (below) and boot it, you'll have to create the /dev/tty
entry:
/etc/mknod /dev/tty c MM 0
(replacing the MM with the correct number, of course). (This has all already
been done on the
DOS system.)
cc -c c.c
as l.s
mv a.out l.o
and then you're ready to build a new Unix (note the use of a different
file name for this system image; that way, if you make a mistake, you
can go back and boot the original system):
ld -x l.o m40.o c.o ../lib1 ../lib2
mv a.out /nunix
Note that to increase the number of KL11/DL11 lines, you'll have to i) edit
kl.c (found in the sys/dmr directory), ii) re-compile it, and then iii) add
the modified version to the library:
ar r ../lib2 kl.o
Note also that the free demo version of Ersatz-11 doesn't support DH-11s,
etc, so if you want more than one tty you have to use KL11's. The demo
version does support the DZ-11, but there is no DZ-11 driver in the 'vanilla'
V6 distribution. I do have a DZ driver for V6 (see
below),
but before you try and add that, add the DOS device
(below)
first.
Note also that Unix uses all 406 cylinders of an RP03, but Ersatz-11 by default assumes the size stated by DEC, i.e. 400 cylinders. So you need to say something like:
mount dp0: C:\Unix\UnixLarge. /RP03 /Cylinders:406otherwise you'll get odd errors.
Finally, note that in Ersatz-11, when you dismount the last drive on a controller, it automatically deletes the controller. If you then (forgetfully) try and touch a disk (e.g. 'icheck' or 'mount' or something) on that controller, the system will crash with a kernel NXM fault when it tries to touch the controller. For that reason I habitually mount a null pack as the last drive on all the controllers, e.g.:
mount dk7: null: mount dp7: null:to prevent the controller being deleted when the last real pack is dismounted.
I have written a V6 driver for it, and a suite of Unix commands which allow you to i) grab a file off the Windows file system (both binary and text mode), and ii) issue various commands to the emulator. Since getting files into the Unix filesystem is painful (at best) without this, I recommend adding this as step #1 once you have your Unix running.
[The thing I use this for the most is editing files. 'Vanilla' V6 Unix only has a line editor ('ed'), and while it's usable, it's truly painful compared to even a poor visual editor. We did have a nice Emacs on the PDP-11 V6+ at MIT, but while have located a copy, I have yet to make it run on vanilla V6, and make it available; I think there's a 'vi' available for V6 too.
So to edit a file, I almost always (except for tiny changes) I tend to keep the master copy on the Windoze computer, edit it there (I myself use, and swear by, Epsilon, which is small, fast, very customizable, and allows me to run a Windows command line interpreter in a buffer), and move the edited result to the Unix machine; the command I have to do this is so short/simple that it's very painless to do this.
If I do edit a file a bit on the Unix machine, and then want to edit it more significantly, I slurp it over to the Windows filesystem, using the 'filex' command (below), and then copy it back as above when I'm done.]
The driver is available here, and the header file it (and the Unix commands which interact with the DOS device) use is here. To get them into the Unix, I used the following kludge (which is another reason to get this running ASAP :-) - perhaps you can think of a better one!
[In fact, it's so painful that I decided to make a system/disk pack image with the DOS device (and the 'hostrd' command) all pre-loaded (see above), so unless you're a masochist, you can ignore the rest of this section, and just go there instead!]
Anyway, here's the whole gory procedure I used to get files into the Unix (before I had the DOS device working!):
mke blank 4872
ins blank dos.c 0 0
mount dk2: blank.
dd if=/dev/rrk2 of=dos.c count=19
ed dos.cgo to the end of the file ("$[Return]"), and poke around ("-[Return]", "p[Return]" - for those of you who don't remember how to use 'ed' :-) and get yourself on the first line after the last line of code. (The files all have a blank line or two at the end of them to make this easier.) Type ".,$d[Return]" to get rid of everything else in the file, "w[Return]" to save it, and "q[Return]" to exit 'ed'.
mount dk2: dos.cto the emulator, and be able to read the file out of the pack, but for some reason that doesn't work, so you have to add these two additional steps.)
You can now move them to the right place (/usr/sys for dos.h, and /usr/sys/dmr for dos.c), and compile the DOS driver and add it to the device library:
cc -c -O dos.c ar r ../lib2 dos.o rm dos.oNext, you have to edit l.s and c.c (in /usr/sys/conf) to add the appropriate code to call the DOS driver (see above on how to create l.s and c.c); in c.c, use 'ed' to add a line like this to 'cdevsw':
&dosopen, &dosclose, &dosread, &doswrite, &dossgtty, /* DOS: = NN */(again, where NN is the correct major device number) and in l.s add this to the vector area:
. = 110^. dosio; br4and this to the linkage area:
.globl _dosintr dosio: jsr r0,call; _dosintr(If you've never done this before, don't worry, it's easy to see, looking at the file, where they need to go.) You can now build a new Unix (see above for how to do that) with the DOS device driver in it, and boot it.
As your penultimate step, you need to create the DOS special device file in /dev; if your major device number for the DOS device is X, you need to say:
/etc/mknod /dev/dos c X 0(Now, can you see why I provided a pack with this all pre-loaded? :-)
Finally, configure the DOS device on the emulator (remember you need to exit to the emulator command level to give this command):
mount do:Now you're ready for the commands which use it...
This command is pre-loaded in the 'DOS' pack; the source is in /usr/src, and the binary is in /bin. You may wish to rename the binary to something shorter, since it gets used a lot: I call mine 'hrd'. If you have to download it, the source is available here. This (and a bunch of the other commands below) also need strlen(), there's a copy of that here.
To use it, just say:
hrd {host-name} {unix-name}if you don't provide {unix-name}, it will try to write the file to {host-name} on the Unix filesystem; if that fails, it will try a filename of just the last component of the host-name in the current directory of Unix. I.e. if you say:
hrd ../windows/desktop/fooyou'll wind up with a file 'foo' in your Unix current directory.
It has several flag arguments; the first is "-a", which tells it that the file being transferred is an ASCII text file (e.g. C source), and that it should do Windows->Unix text conversion (i.e. replace CRLF newline sequences with just the plain LF Unix expects - the V6 C compiler will barf on CRLF's). This flag defaults to on (since I mostly use this command for transferring text files in the edit/compile/test loop), you don't actually need to give it.
The "-b" flag disables that behaviour (i.e. it transfers the file as a raw file). (If you're trying to read a binary file into the machine, don't forget to give this one, or the file will likely be roached.)
The -t flag suppresses printing of the total amount transferred (given in blocks+bytes).
hc dismount dk2:What's the point of that, you say? I mean, you can just exit to the emulator and give the command. Well, you might be logged in on a non-console tty (more on this below below - although on those 'hc' can't show you the output of the emulator). Also, it allows me to have shell command files which e.g. tell the emulator to mount up a pack, and then do a Unix 'mount' of the pack.
hrd /unix/sys/foo.cJust do:
hcd /unix/sysand then you can:
hrd foo.cas you go around the edit/compile/test loop. Plain 'hcd' on the command console will print the emulator's current working directory.
sc 0 ^D login: root sc 173030(The sc 173030 is because if you don't do it right away, you'll probably forget to do it before rebooting! :-) Yes, yes, I know this is the same as "hc set switch X", which I could easily put in a Unix shell run file, but it was written before "hc" was.
First, the 'date' command only take a 2-digit year number. Second, even if you fix that, the ctime() library routine has a bug in it that makes it stop working in the closing months of 1999. (IIRC, the bug is that the number of 8-hour blocks since the epoch - January 1, 1970 - overflows a 15-bit integer at that point. 'Vanilla' V6 C doesn't have unsigneds.)
I have 'fixed' copies of date.c and ctime.c; here and here. You can download them and install them; ctime.o goes in /lib/libc.a:
ar r /lib/libc.a ctime.oThe 'date' command has been extended to support 2- and 4-digit year numbers (to be upwardly compatible, the 2-digit ones assume 19xx). It has also been extended (for forgetful people like me :-) so that if you type:
date -it will tell you what order the arguments go in.
The 'fix' in ctime() is really kludgy - sorry, I was in a hurry! It will also break in another 15 years or so (when the number of 8-hour periods overflows 16 bits). Someone else can fix that one!
There are a number of programs that are linked with ctime.o, and will produce bogus results unless they are linked with the fixed one. I don't have a complete list, but one major one is 'ls'.
It also needs strlen(), there's a copy of that here; and a couple of header files - stat.h, sgtty.h and sig.h - which are available here, here, and here. I honestly don't recall where the header files came from: I don't know if I edited them out of V6 kernel header files (it looks like that might be where stat.h and sig.h came from), or if they came from the Shoppa disks, or if I created them from scratch (possibly for sgtty.h). Anyway, it doesn't matter - I needed them, and here they are now!
(Why does the source call for them to be in "/lib/h" (of all non-standard places)? Well, feel free to put them whereever you want, but as to why I have them there.... I always felt the whole /usr thing for files which aren't actually user files was... confused. So I generally frown on /usr/{anything} - although some things, like /usr/bin, I just have to live with. But why "h" instead of "include"? Less characters, of course! Duhhh!)
It has one bug (which I'm still trying to work out how to solve): it opens the terminal in 'raw' mode (so you can hit 'Space' for each new screen), and it is also useable as a filter (e.g. "nm /unix | more"), but as a result it you say just 'more' (with no arguments) you can't escape its clutches (no EOF in raw mode, right)? If you're running single-user, you'll have to reboot the system!
I personally use the TELNET line mostly, because I prefer the look of the Windows TELNET client - black letters (font selectable) on a white background I much prefer to the VT100 emulation on the Ersatz-11 consoles! And you can't use a DH or anything else, because the free (demo) version of Ersatz-11 doesn't support them.
Anyway, here is the driver source (goes in dmr, see instructions above on how to compile a driver and add it to lib2), and here are the l.s and c.c. entries:
. = 270^. dzin; br5 dzou; br5and:
.globl _dzrint dzin: jsr r0,call; _dzrint .globl _dzxint dzou: jsr r0,call; _dzxintand:
&dzopen, &dzclose, &dzread, &dzwrite, &dzsgtty, /* dz = XX */Note that this does not use the standard DZ-11 CSR and vector address in Ersatz-11; you will need to configure it there thusly:
assign yza0-7: telnet: set yza: csr=770000 vector=270And don't forget that these lines will only operate i) when Unix is running multi-user, ii) there have to be /dev/tty? special files for them, and iii) you have to edit /etc/ttys to enable them for login listeners.
You just need a device that's big enough to gain access to the complete image file. Unix will read the disk size (in the superblock) and not try and go any further than that (just like it ignores the swap area on an RK-based system). Since an RP pack is far larger than an RL, it fits the bill nicely (but make sure to use minor device 0 or 4; the others aren't large enough to cover a whole RL02 pack, or don't start at cylinder 0). (See above for how to build new Unix loads with different devices, to add the RP driver to your Unix.)
Unless you have a real need for an RL driver (why, I can't imagine), you can skip the rest of this section. Anyway, if you have some use for a V6 RL driver, it's here. Here are the lines to add to l.s for it:
. = 160^. rlio; br5and:
.globl _rlintr rlio: jsr r0,call; _rlintrand for c.c (two lines, since it's a block device: one for bdevsw, and one for the raw device in cdevsw):
&nulldev, &nulldev, &rlstrategy, &rltab, /* rl = X */ &nulldev, &nulldev, &rlread, &rlwrite, &nodev, /* rl = XX */I also have an RL bootstrap (from the Shoppa disk; the source was lost, but I disassmbled the actual bootstrap and re-created the source, checking that the assembler output matched the binary I started with); it's available here.
Actually, there were two different ones; the source for the other (also retrieved by disassembly) is here. I guess I ought to comment the latter a little better; I read through the code in order to be able to turn the magic numbers in the disassembled code into named constants, but I didn't do much more than that to make it readable.
All of them were compiled with, and run under CygWin. If you don't already have this (it's pretty cool), or feel like dealing with it, all you need to do is download the executable versions (below), and also the CygWin library DLL, and put that somewhere the commands can find it (e.g. in the same directory as the commands).
Source is here, Windows binary here. It requires a couple of hacked header files (to produce the right types in the Intel GCC compiler), wino.h and wfilsys.h, available here and here.
To run it, as follows:
fx {filesysfile} {hostname} {unixname}Note that unixname is relative to the filesystem the file is in; i.e. if you have UnixSrc up on RK2, and mounted as /src, to read the file /src/foo/bar.c, you'd say:
fx UnixSrc bar.c /foo/bar.cThere's also a mode to read given only the inode number (for roached disks). 'Use the source, Luke!'
One nice feature is that it will share access to the disk image with Ersatz-11 - i.e. you don't have to 'unmount' a drive from Ersatz-11 before you can read it with this. So that make it really convenient to go around the edit/compile/test loop on Unix (compiling) and Windows (editing): 'fx' it to the Windoze box, edit it, 'hrd' it back to Unix.
mke {filesysfile} {nblocks}Note that before you can mount the resulting disk pack under Unix, you need to create a Unix file-system on it with mkfs.
ins {targetfile} {sourcefile} {offset} {nblocks}If the {nblocks} argment is given as zero, the entire file is inserted. This command is useful for things like i) inserting a bootstrap program in block zero, ii) extending a 4000 block RK disk image file to 4872 blocks so Unix can swap off of it, etc.
stripcr {sourcefile} {outputfile}Note: The code is really simplistic, and won't grok CRs that aren't paired with LFs. For more sophisticated CR stripping, use 'hrd'.
bex
dmf [flags] {sourcefile} {blkno}Flag arguments: -a dumps in ASCII bytes, -d in decimal words (default is octal words), -o prints the addresses in decimal (default is octal).
To compile, tar needs a couple of tweaked header files, available here, here, and here.
Note: The functionality of this program is suspect; the changes made to get it running were so minimal. E.g. simply saying:
tar tvf v7.targives a directory checksum error. However, if the blocksize is specified as 1, i.e.:
tar tvfb v7.tar 1it works - even though it's a file, not a tape! It may be that something about file reading under Windows (where, with the Standard I/O package, one has to specify whether to open files in the ASCII or binary modes) doesn't work properly.
Also, the date-setting functionality doesn't seem to work, so if you extract the things from an archive, their 'last-write' date isn't set properly. Feel free to fix either of these, and let me know how!
Back to JNC's home page
© Copyright 2014-2015, 2019-2020 by J. Noel Chiappa
Last updated: 18/September/2020