If you're wondering why V7, it was the first portable UNIX and the last public Bell Labs distribution. It's free software now. V6 is the one for which the first BSD was a supplement for and that goes with Lion's commentary and was partially rewritten as xv6. PWB/UNIX, which eventually evolved into Syatem V, also derived from V6. So in a sense V6 is the first portable public common ancestor and V7 is the last.
To be fair v6 was able to be ported, it just wasn't as easy (I know because I ported it to native VAX) the compiler was the hard part, along with the evil context switch primitives
That comment is only partially related to those primitives - the primitives are used in a bunch of other places with more obvious functionality - it's the doing something strange because the SSWAP is set that you're not supposed to understand - where that bit is set is miles away and not obviously connected (plus there's that "return(1)" from a routine that no one who appears to call it uses the result).
V7's save()/restore() are more portable and easier to understand
I feel like the 64KB of memory available to processes made it a total non-thought on system designers. Not quite like later architectures where it'd be 2GB, 4GB, or 16EB.
64 kB was the limit for a single process, but most PDP-11 had a memory-management unit with paging that extended the address space to either 18-bit (256 kB) on the older models or to 22-bit (4 MB) on the later models.
So you could execute in parallel multiple processes even if each process used its entire 64 kB space.
Moreover, it was possible to have distinct 64 kB address spaces for code (the UNIX "text" segment) and for data (the unix "data" and "bss" segments + the heap + the stack).
Therefore you could have simultaneously in a 4 MB memory at least 32 processes using the maximum 64 kB code + 64 kB data. It is likely that typically around 100 processes could be resident in the main memory.
Because on PDP-11 each process was limited to a small fraction of the main memory it did not make sense to enforce any additional memory limits.
On other computers where a single process could access all the memory, e.g. on DEC VAX for which most BSD versions were developed, enforcing memory limits became necessary.
Have you any idea how much memory cost back then? Individual magnetic cores hand threaded to make small planes if memory.
Late 70s we bought 1.5Mb of memory for our mainframe, we paid more than a million dollars. The phone in your pocket is 10,000 times faster, has 2000 times more memory, and is 10,000 times cheaper than that mainframe
No one practically worried about supporting 2Gb systems, people wrote small, tight code, spending time to make things fit
Well, read it again and you'll find out there was nothing non-thought. In the given context, computers with 64kB available to programs were actually pretty high caliber and again that 64kB had nothing to do with system limit but really resource constraint.
Once upon a time, we used to multi-task (background processes) several programs at a time within that constraint. It was the environment in which we found ourselves, so we just accepted it as all the space in the world.
Wrong. Without fixed stack size limits you can tune heap vs stack usage dynamically. Eg allow deeper recursions when needing less heap memory. Eg it would allow longer lists. Stack overflows would need to be checked dynamically against the brk.
IIRC, the stack started at the top and grew downwards, while the heap started somewhere near 0 and grew upwards; might have been similar under RSX. I can't remember what happened with NULL pointers, though.
This is still how memory is setup on things like microcontrollers (if you are foolish enough to use a heap, that is). With MMUs of course there is no particular reason to do it like that.
Stack growing downwards is enshrined in processor instructions; people like to point at how C has no string types but that right there is another trillion dollar security issue decision..
Not all microcontrollers have too little RAM for a heap. Sure, putting a heap on your little Atmega182p is foolish, but some have 1 megabyte of RAM or more. For example, the Teensy 4.1 is a beast, relatively speaking.[0]
Not disagreeing with you. Just pointing out that not all microcontrollers are puny. Check your IC specs before deciding things.
In the context of microcontrollers I think the issue with using a heap is not per se that you do not have enough memory, but more that your memory usage becomes unpredictable or very hard to make any guarantees about.
Why would the heap make memory usage unpredictable? How’s it any different than the stack? For example, if I stack allocate 1000 bytes, leaving the function frees those bytes (by destroying the frame). If I allocate those 1000 bytes on the heap instead and make sure to free() it, there’s no change to memory usage.
It's easier to figure out what calls what and the maximum stack depth of some or all of the program. Trying to track maximum usage based on allocations is a lot harder, especially when you have to deal with fragmentation.
I'm reminded of the seemingly absurdly large limits of function arguments count in Common Lisp implementations. They're so large because you often write:
(apply #'some-function some-data)
which calls some-function with some-data becoming the argument list to the function.
As I understand it the stack size limit is because a process's threads share the same address spaces so basically where would you put all their stacks? With one stack you just start it from the top of (virtual) memory and put the heap at the bottom and when they meet you've run out of memory. Infinite stack!
But with lots of threads there aren't enough spaces for them to start from. At least on 32-bit. On 64-bit I think you could have effectively infinite stack, or at least very very large (like 1 GB) stacks. But as far as I know nobody has bothered to do it.
I guess an alternative solution would be to put the stacks in different address spaces somehow, but that could get very complicated (now you have two kinds of pointers). Are there any old systems that had separate heap and stack address spaces?
>I guess an alternative solution would be to put the stacks in different address spaces somehow, but that could get very complicated (now you have two kinds of pointers). Are there any old systems that had separate heap and stack address spaces?
On 16-bit x86, memory was divided into 64K segments and you could have separate ones for code, data and stack. Having effectively a different address space for code isn't a problem, but languages like C expect that you can pass around pointers to stack variables as well as global/heap ones. So now every data pointer needed to be a "far" one (32 bits, segment:offset), and to dereference it the segment portion would usually have to be loaded into the single "extra" segment register that isn't dedicated to code/data/stack.
With data and stack sharing the same segment, you could use 16 bit pointers for everything and the code was a lot more efficient.
Stack was really meant to save parameters for function calls and local variables for functions or in your terms sub-routines. In a well organized program, usually you would not need much stack space unless there was something went wrong, such as a recursive call without termination condition etc..
Even local variables of functions can take up a lot of room can take quite a lot of space. Especially when you minimize use of the heap. Using the stack instead of heap where possible makes for clean automatic free-ing of unused memory.
> In a well organized program, usually you would not need much stack space
But that's only because stack space is currently limited. If stack space were as unlimited as heap space then there would be absolutely nothing wrong with using as much stack space as you wanted. It would be better even - stack space is much quicker to allocate and free than heap space.
I really don't know what you mean. Forth has two stacks that can grow infinitely.
Trying to do everything with tail calls in Forth puts you in a position much like any other language: Okay, you can have a single overarching state machine with top-level routines, but you can't factor anything into subroutines because there's no way to 'return'.
There was a previous post from that blog which was not accurate so it’s worth pointing it out.
Also setrlimit and getrlimit let you query and set stack size which are settable via ulimit utility as well. I don’t want to roll my eyes here but this is some serious FUD.
others have pointed out that this article is talking about v7 unix and so this comment is incorrect -
and the so-called 'inaccuracy' you point out in the previous thread was the entire point of the article for that thread - that programmers need to be aware of the stack size and manage it explicitly, and many do not.
If you're wondering why V7, it was the first portable UNIX and the last public Bell Labs distribution. It's free software now. V6 is the one for which the first BSD was a supplement for and that goes with Lion's commentary and was partially rewritten as xv6. PWB/UNIX, which eventually evolved into Syatem V, also derived from V6. So in a sense V6 is the first portable public common ancestor and V7 is the last.