Class 3 CS 372H 25 January 2011 On the board ------------ (One handout) 1. Last time 2. gcc calling conventions 3. PC emulation 4. Privileged vs unprivileged mode 5. User-level/kernel interaction --------------------------------------------------------------------------- 1. Last time --PC architecture, X86 instructions, --How do we go from C code to a running program? --requires compiler, assember, linker, and loader --here's the picture: gcc as ld loader .c --> .S --> .o ---> a.out -------> memory ^ / .c --> .S --> .o --clarification: --"ld" is the *linker*; it is not the loader, which serves a different function. --the *loader* takes a binary executable from the file system, puts it in memory, and creates a process to begin executing it. 2. gcc calling conventions call 0x12345 [ pseudo: pushl %eip movl $0x12345, %eip] ret [ pseudo: pop %eip ] --last time we saw how call and ret interact with the stack --call: updates %eip and pushes old %eip on the stack --ret: updates %eip by loading it with stored stack value --but what happens to a function's state, that is, the registers, when a function is called? they might need to be saved, or not. --purely a matter of convention in the compiler **not** hardware architecture --here's what gcc does: --[ draw blocks of code: main f g draw registers: eip ebp eax ecx draw stack ] --at entry of a function: looks like this: arg 3 arg 2 arg 1 -->esp [ret_addr] [fill in picture above] %eip points at first instruction of function %esp+4 point at first argument %esp points at return address --after ret instruction: %eip contains return address %esp points at arguments pushed by caller %eax contains return value (or trash if function is void) %ecx, %edx may be trashed %ebp, %ebx, %esi, %edi need to look the way that they did at the time of the call --in other words: %eax, %ecx, %edx are "caller save": caller's job to push them on the stack if it wants to save them %ebp, %ebx, %esi, %edi are "callee save": callee's job to push them on the stack after function call, and pop them (meaning restore their values by removing them from the stack) just before doing return --here's the picture of the stack when one function calls another: +------------+ | | arg 2 | \ +------------+ >- previous function's stack frame | arg 1 | / +------------+ | | ret %eip | / +============+ %ebp-> | saved %ebp | \ +------------+ | | | | | local | \ | variables, | >- current function's stack frame | callee- | / | saved vars,| | etc. | %esp-> +------------+ / --%esp moves to make stack bigger/smaller --%ebp points at saved %ebp from previous function --saved %ebs form chain; can walk stack --arguments and locals at fixed offsets from ebp --function prologue: pushl %ebp movl %esp, %ebp --function epilogue movl %ebp, %esp popl %ebp ret --example [see handout] 3. PC emulation --QEMU does exactly what a real PC would --But it is implemented in software, not hardware --Runs as a normal program on "host" operating system. --The layering looks like this: | JOS | ------------------------------- PC emulator| Web browser | ... ------------------------------- Linux ------------------------------- PC hardware ------------------------------- --Uses normal programmatic constructs (if statements, memory, etc.) to emulate processor logic and state --Stores emulated CPU registers in global variables int32_t regs[8]; #define REG_EAX 1; #define REG_EBX 2; #define REG_ECX 3; .... int32_t eip; --Stores emulated physical memory in QEMU's memory char mem[256*1024*1024]; --See handout --Simulate I/O devices, etc., by detecting accesses to "special" memory and I/O space and emulating the correct behavior: e.g., --Reads/writes to emulated hard disk transformed into reads/writes of a file on the host system --Writes to emulated VGA display hardware transformed into drawing into an X window --Reads from emulated PC keyboard transformed into reads from host's keyboard API --------------------------------------------------------------------------- Summary of lecture 2 and first half of lecture 3: --covered PC and x86, which is the platform for the labs --illustrated some important CS ideas --stored program computer --stack --memory-mapped I/O --equivalence of software and hardware --------------------------------------------------------------------------- Admin: --lab due tomorrow --------------------------------------------------------------------------- 4. Privileged vs. unprivileged mode --the difference between these modes is something that the *hardware* understands and enforces --the OS runs in privileged mode --can mess with the hardware --can manipulate OS abstractions (obviously, but worth repeating) --users' tasks run in unprivileged mode --cannot mess with the hardware --sees a picture of a virtual machine --process: --address space, open files, virtual CPU --if one process needs to interact with another?..... --whole point to processes is to share the resources of the machine (one waits for the input, and the other can do stuff.) [going to go top-down here: discuss where processes come from (and how they interact with the kernel), then how they get multiplexed, and finally what they really are.] 5. User-level/kernel interaction How do programs start running (i.e., how do processes get started?)? How do they interact with the OS explicitly? --we will often refer to terms like "user-level program", "process", the "OS", and the "kernel". --the idea is that a user-level program is just a "user" of the OS. [note that programs of course interact with the OS implicitly] (1) programs run inside proceses, which get started at the *shell* --shell is a program that the kernel loads as one of its first programs (2) programs can ask the OS to do things for them. But there are still two questions: a. how does the shell create processes? b. how do programs ask the OS to do things for them? --turns out that both questions have the same answer....... --syscalls! --API exposed by the kernel to the user --for any system call, type "man 2 " to get the documentation. --that is, **syscalls are how user-level programs ask the operating system to do things for them ** --when a user-level program invokes the kernel via a syscall, it is called *trapping* to the kernel app | (open) v --------------------------- ^ | | |____> [table] open() | ..... | return -------- --let's look at how the shell starts other programs (i.e., creates processes) --calls fork(), which creates a copy of the shell. now there are two copies of the shell running --then calls exec(), which loads the new program's instructions into memory and begins executing them. while (1) { write(1, "$ ", 2); readcommand(command, args); // parse input if ((pid = fork()) == 0) // child? exec(command, args, 0); else if (pid > 0) // parent? wait(0); //wait for child else perror("failed to fork"); } --how can shell wait for the end of a process? --with wait() or waitpid() system calls --why are fork() and exec() separate? [an innovation from the original Unix. possibly/probably lucky design choice at the time. but turns out to work really well. allows the child to manipulate environment and file descriptors *before* exec, so that the *new* program may in fact encounter a different environment] consider an example alternative, this from Windows: BOOL CreateProcess( name, commandline, security_attr, thr_security_attr, inheritance?, other flags, new_env, curr_dir_name, .....) [http://msdn.microsoft.com/en-us/library/ms682425(v=VS.85).aspx] there's also CreateProcessAsUser, CreateProcessWithLogonW, CreateProcessWithTokenW, ... why? because whoever calls CreateProcess() (or its variant) needs to perfectly configure the process before it starts running. with fork(), whoever calls fork() **is still running** so can arrange to do whatever it wants, without having to work through a rigid interface like the above. allows arbitrary "setup" of the process before exec(). --what are other syscalls we have seen so far? [including these so you know what the basic interface to a Unix-like OS is.] --int open(char*, int flags, [, int mode]); --int read(int fd, void*, int nbytes): --int write(int fd, void* buf, int nbytes); --off_t lseek(int fd, off_t pos, int whence) --int close(int fd); --int kill(int pid, int signal) --void exit (int status) --int fork(void) --int waitpid(int pid, int* stat, int opt) --int execve(char* prog, char** argv, char** envp) --int dup2 (int oldfd, int newfd) --int pipe(int fds[2]) --what happens if a system had two important users, and one of them did this? for (i = 0; i < 10; i++) { fork(); } while (1) {} [answer: one of the users gets a LOT more of the CPU than another] ? --what behavior do you want? [this actually corresponds to research. OSes are only just applying resource containers.]