Introduction
Announcements

Schedule
Labs
Assignments
TA office hours

Tests, exam

Topic videos
Some course notes
Extra problems
Lecture recordings

Discussion board

Grades so far

Basic process memory layout

The basic process memory layout is the result of "linking" (combining the various .o and .a (or .so) files into an executable program format) plus arranging the disk file's data into main memory (allocating data storage and such). The gcc command performs the linking process (by running ld). Then we have a file on the disk which we can execute with an exec() function. The following notes are primarily about what happens to memory layout at the time of exec() and after.

Process memory is divided into a "text segment" and a "data segment".

The "text segment" is the code, the machine language instructions that are your program. It is made read-only via the MMU.

The "data segment" is more complicated. It is not read-only, far from it; it changes as your program executes. Some of its components are:

Variables with global lifetime are allocated in the data segment. Scope is an error-checking issue for the C compiler; global versus stack is a lifetime issue. Function-static is in the global area. But "auto" variables can't have a fixed address. They are allocated on the stack.

(Insert (virtually speaking) brief reminder of CSC 258 material regarding stack use for modern-style procedures (functions))

The text segment is shared between multiple processes executing the same program! This was a useful memory savings in the early unix days. Still nice. Not a problem because the text segment is unmodifiable.

The stack may be a separate segment. Whether it's part of the data segment or separate, it is private. Each process gets its own.

Virtual memory

What happens when memory fills up? lots of processes... not all running...

The "MMU" maps addresses, a "page" at a time. It sits between the CPU and the main memory unit, conceptually; these days typically built in to the CPU.

paging stuff out: write to disk

The MMU enforces some of its access restrictions implicitly. You can't refer to a memory address which is part of a different unix process. Except by using numbers below zero or above the size of your process. In that case, the page-fault handler signals segfault. i.e. the page-fault handler can signal an OS-level page fault if it's a virtual page fault and not just a physical page fault.

"Copy-on-write": after a fork(), typically one process does an exec(), or an exit(), pretty soon, most of the time. So in the interim, don't duplicate all the data pages. (not an issue for code pages!) Mark the data pages as read-only in the MMU configuration. If a process tries to write, this generates a hardware page fault; the OS duplicates that particular page and then marks them both as read-write.

Much more about all this in CSC 369.

sbrk(): allocates more pages at the end of your data segment. sbrk() system call returns the new top of memory. So malloc() begins by calling sbrk(0) to find out where the end of memory is. It uses this as the "heap":

heap is managed by complex algorithms... fragmentation issue

Can write your own memory allocator! minimal kernel! sbrk() means increase the data size allocated to this process; that's all. sbrk() requires MMU adjustments, thus restricted to kernel. All else is userland. Malloc is in the C library, just calls sbrk().

(Actually, sbrk() is in the C library and the kernel entry point in question is brk(), but that's a minor point.)

System calls

So, how does that syscall work anyway?