Here I'm trying to understand what happens when I run
./hello
#include <stdio.h>
int main() {
printf("Hello!\n");
}
a simple "Hello World" program written in C, in Unix -- what I'd have to do if I wanted to write an OS that could execute it.
I'm going to assume that ./hello
is statically linked, because that sounds simpler to deal with. It's worth noting that a statically linked hello
is 868K on my machine. Eep.
I compiled it using
gcc -static hello.c -o hello
Any (nice!) comments or clarifications are appreciated.
To run a program, I have to be able to find the program. So there would need to be some kind of filesystem and I would need to read the file from somewhere.
In a Unix system, executables are in the ELF format.
So I would need to copy the "text" of the program somewhere.
There is a string in the program. It needs to go somewhere.
This program doesn't actually allocate memory, so perhaps it does not need a heap and it doesn't matter where the heap pointer is. It does need a stack. stack overflow question on how the stack works in assembly
hello
has some system calls in it. I found this out by running
objdump -d -M intel hello | grep 'syscall'
syscall
is an assembly instruction for making a system call. That looks like
401385: b8 03 00 00 00 mov eax,0x3
40138a: 0f 05 syscall
The number stored in eax
is the system call that is called. In this case, 3
There are 119 instances of syscall
, and it's using several different system calls. This is worrying.
(Explained more in this stackoverflow question)
I have no idea how the OS would check up on the program. I guess it doesn't just let the program run, but takes away control periodically and makes sure the stack pointer hasn't moved too far. How would it take away control? Hmm.
When there is a stack overflow I guess it sends a signal to the program, which is a POSIX thing.
I do not understand this.
There are no malloc
s in the program, so I would not need to allocate memory for it or anything.
What else?!??
- How long would this take for a human (where human = me) to write from scratch?
- Is there a way to write a smaller program with less system calls and magic? There are like 50 system calls and what are they even doing?
- Do I need a heap if I never use
malloc
? - Could I write my own printf in assembly that does less and is simpler? Just printing a string is pretty easy...
- How do I kill a program?
Regarding your #2 above, the interrupt gets called periodically by the hardware, as @danellis remarked. But there's also a very important additional circumstance in which an interrupt occurs: there is a machine instruction which causes one when it is executed, and this is precisely how the
hello
program notifies the kernel that it wants to perform thewrite
call. (At least, that's definitely how it works on a System/360 machine, which I am ashamed to say is the most recent architecture I am really familiar with. Most of my career has been at the source code level.)You also asked about stack overflows. @danellis described how the stack grows downwards in memory. When your program tries to access an address off the bottom of the stack, the MMU generates an interrupt. The kernel gets control, and checks to see if your process is allowed to have another page of stack space; if so it gets the MMU to allocate one and map it into your process's address space, and then everything continues normally. But if it decides to refuse the request, perhaps because your process is marked in the kernel as only being allowed a limited amount of stack space, it instead sends your process a
SEGV
(‘segmentation violation’) signal, which then gets handled as usual; the default is for the process to exit immediately and the kernel copies its entire address space into a file namedcore
in the process's working directory. The shell commandulimit
, which is a thin wrapper around the kernel'sulimit
system call, is for setting the stack size and other limits.I don't know if there's any way for the process to find out how big its stack is. It can use
ulimit
to find out the maximum allowed size. I don't think there's a way for it to catch the interrupt generated by the MMU when it starts a new stack page. It can change the way it handles theSEGV
signal. One thing it can do is to ask the kernel to transfer control to a "signal handler" function in case ofSEGV
, instead of killing the process instantly. The signal handler can try to reduce the size of the data segment so that the stack has more room to grow.