400 likes | 414 Views
Learn how the execve() function in Linux executes programs, handles arguments and environments, and overlays calling processes.
E N D
제30강 : exec() exec()
EXECVE(2) Linux Programmer's Manual EXECVE(2) NAME execute program SYNOPSIS #include <unistd.h> int execve (const char*filename, char *constargv [], char *constenvp[]); DESCRIPTION execve() executes the program pointed to by filename must be either abinary executable, or a script starting with a line of the form "#! interpreter [arg]". …. argv is an array of argument strings passed to the new program. envp is an array of strings, conventionally of the form key=value, which are passed as environment to the new program. Both, argv and envp must be terminated by null pointer. The argument vector and environment can be accessed by the called program's main function, when it is defined as int main(int argc, char *argv[], char *envp[]). execve() does not return on success, and the text, data, bss, and stack of the calling process are overwritten by that of the program loaded. The program invoked inherits the calling process's PID, and any open file descriptors that are not set to close on exec. Parent passes arguments to child Child’s a.out (env_var_name, env_var_value) (child program will invoke exit() system call at termination -- compiler puts it)
main() { loop: printf (prompt) scanf (eg cat ch1 ch2 > ch12) pid=fork() if (child) exec(“/bin/cat”) else /* parent */ wait() go to top } (1)prompt % (2) command % cat ch1 ch2> ch3 command line argument parent sh
main() { loop: printf (prompt) scanf (eg cat ch1 ch2 > ch12) pid=fork() if (child) exec(“/bin/cat”) else /* parent */ wait() go to top } (1)prompt % (2) command % cat ch1 ch2> ch3 command line argument parent sh parent’s variable
main() { loop: printf (prompt) scanf (eg cat ch1 ch2 > ch12) pid=fork() if (child) exec(“/bin/cat”) else /* parent */ wait() go to top } child sh parent sh main() { loop: printf (prompt) scanf (eg cat ch1 ch2 > ch12) pid=fork() if (child) exec(“/bin/cat”) else /* parent */ wait() go to top }
main() { loop: printf (prompt) scanf (eg cat ch1 ch2 > ch12) pid=fork() if (child) exec(“/bin/cat”) else /* parent */ wait() go to top } child sh parent sh main() { loop: printf (prompt) scanf (eg cat ch1 ch2 > ch12) pid=fork() if (child) exec(“/bin/cat”) else /* parent */ wait() go to top }
cat ch1 ch2 fork() call newproc() newproc() copy proc copy image rerturn(0) exec() overlay wait() swtch() parent retire proc[0] child arise return(1) { sh (parent) main() { If (PID=fork()) PID=0 exec () /*child*/ PID<>0 wait() /*parent*/ 2 sh { to create a child 1 { {
copy parent’s image fork() call newproc() newproc() copy proc copy image rerturn(0) exec() overlay wait() swtch() parent retire proc[0] child arise return(1) sh (parent) main() { If (PID=fork()) PID=0 exec () /*child*/ PID<>0 wait() /*parent*/ Stack: sub-shell (child) main() { If (PID=fork()) PID=0 exec () /*child*/ PID<>0 wait() /*parent*/ Stack: 2 sh 1 3* Also copy parent’s ppda entered newproc() entered fork() entered newproc() entered fork() 3* child created (ready)
fork() call newproc() newproc() copy proc copy image rerturn(0) exec() overlay wait() swtch() parent retire proc[0] child arise return(1) sub-shell (child) main() { If (PID=fork()) PID=0 exec () /*child*/ PID<>0 wait() /*parent*/ Stack: sh (parent) main() { If (PID=fork()) PID=0 exec () /*child*/ PID<>0 wait() /*parent*/ sh 5 1 child (sub-shell) created 4 entered newproc() entered fork() 3* child created (ready)
fork() call newproc() newproc() copy proc copy image rerturn(0) exec() overlay wait() swtch() parent retire proc[0] child arise return(1) sh (parent) main() { If (PID=fork()) PID=0 exec () /*child*/ PID<>0 wait() /*parent*/ sh (child) main() { If (PID=fork()) PID=0 exec () /*child*/ PID<>0 wait() /*parent*/ Stack: sh 5 1 6 entered newproc() entered fork() parent wants to rest 6* 3* child created (ready) 7* pop return address 12* exec() at 10 is done back to user mode
fork() call newproc() newproc() copy proc copy image rerturn(0) exec() overlay wait() swtch() parent retire proc[0] child arise return(1) sh (parent) main() { If (PID=fork()) PID=0 exec () /*child*/ PID<>0 wait() /*parent*/ sh (child) main() { If (PID=fork()) PID=0 exec () /*child*/ PID<>0 wait() /*parent*/ Stack: sh 1 entered newproc() entered fork() 3* child created (ready) 7* pop return address 12* exec() at 10 is done back to user mode Child runs on CPU
fork() call newproc() newproc() copy proc copy image rerturn(0) exec() overlay wait() swtch() parent retire proc[0] child arise return(1) sh (parent) main() { If (PID=fork()) PID=0 exec () /*child*/ PID<>0 wait() /*parent*/ sh (child) main() { If (PID=fork()) PID=0 exec () /*child*/ PID<>0 wait() /*parent*/ Stack: sh 8 pop & returns to fork() entered newproc() entered fork() 3* child created (ready) 7* pop return address 12* exec() at 10 is done back to user mode 7*
fork() call newproc() return newproc() copy proc copy image rerturn(0) exec() overlay wait() swtch() parent retire proc[0] child arise return(1) sh (parent) main() { If (PID=fork()) PID=0 exec () /*child*/ PID<>0 wait() /*parent*/ sh (child) main() { If (PID=fork()) PID=0 exec () /*child*/ PID<>0 wait() /*parent*/ Stack: sh 9-1 finish kernel mode 9-2 return to where? same place as parent entered fork() On return from fork() kernel stack becomes empty 9-3 3* child created (ready) 7* pop return address 12* exec() at 10 is done back to user mode
fork() call newproc() newproc() copy proc copy image rerturn(0) exec() overlay wait() swtch() parent retire proc[0] child arise return(1) sh (parent) main() { If (PID=fork()) PID=0 exec () /*child*/ PID<>0 wait() /*parent*/ sh (child) main() { If (PID=fork()) PID=0 exec() /*child*/ PID<>0 wait() /*parent*/ Stack sh Return from fork() PID = = 0 for child 3* child created (ready) 7* pop return address 12* exec() at 10 is done back to user mode
fork() call newproc() newproc() copy proc copy image rerturn(0) exec() overlay wait() swtch() parent retire proc[0] child arise return(1) sh (parent) main() { If (PID=fork()) PID=0 exec () /*child*/ PID<>0 wait() /*parent*/ sh (child) main() { If (PID=fork()) PID=0 exec () /*child*/ PID<>0 wait() /*parent*/ Stack sh 10 entered exec() kernel stack changes 3* child created (ready) 7* pop return address 12* exec() at 10 is done back to user mode
cat ch1 ch2 fork() call newproc() newproc() copy proc copy image rerturn(0) exec() overlay wait() swtch() parent retire proc[0] child arise return(1) sh (parent) main() { If (PID=fork()) PID=0 exec () /*child*/ PID<>0 wait() /*parent*/ sh (child) main() { If (PID=fork()) PID=0 exec( ) /*child*/ PID<>0 wait() /*parent*/ sh cat ch1 10 parameters of exec( ) exec (pathname, argv, envp) 3* child created (ready) 7* pop return address 12* exec() at 10 is done back to user mode
cat ch1 ch2 fork() call newproc() newproc() copy proc copy image rerturn(0) exec() overlay wait() swtch() parent retire proc[0] child arise return(1) sh (parent) main() { If (PID=fork()) PID=0 exec () /*child*/ PID<>0 wait() /*parent*/ sh (child) main() { If (PID=fork()) PID=0 exec( ) /*child*/ PID<>0 wait() /*parent*/ Stack: sh cat ch1 10 entered exec() 11 cat 3* child created (ready) 7* pop return address 12* exec() at 10 is done back to user mode
cat ch1 ch2 fork() call newproc() newproc() copy proc copy image rerturn(0) exec() overlay wait() swtch() parent retire proc[0] child arise return(1) sh (parent) main() { If (PID=fork()) PID=0 exec () /*child*/ PID<>0 wait() /*parent*/ cat (child) main() { Stack: sh new code cat empty user stack 11 cat 3* child created (ready) 7* pop return address 12* exec() at 10 is done back to user mode
P A P A fork P A exec P’A P B (New a.out ) • To create a new process: • Allocate & init proc[], ready queue --- fork() • Allocate & init swappable image • load a.out from disk (overlay) --- exec() • init user stack for main(argv, envp) • Start from main()
Algorithm of exec() • load a.out from disk (overlay) • init user stack for main(argv, envp) • Start from main() Get pathname “/bin/cat” Get inode Is this file executable? …. Copy argv, envp from caller to kernel (first, allocate kernel buffer, then copy) Copy argv, envp from buf to child’s stack (as passing para to main()) ... Yes .. it is like an IPC .. kernel in between
3034 get pathname, map it to inode pointer • uchar() (7686)function name alone=pointer to function, “goto u & get char” • returns string (defining pathname) • pointed to by u -> u.u_dirp: value of 1st argument (pathname) • namei() (7518)maps:[pathname -> *inode] • 3041 Is it executable? • access() (6732) • check mode permission (r_w_x) -- return(1) on error (6779)
prepare to read by readi()readi( ) parameters 6221 • from (file, offset) to (address, count) ip u_offset u_base u_count [3] Memory Address? u_base [1] Which file? Inode (ip) [4] How many? u_count [2] file offset u_offset
3086 prepare to read executable file (6221 readi() needs them) • Read first (u_offset = 0) 8 bytes (u_count=8) • u_base is destination address (6216) set to &u.u_arg[0] • i.e. “read into u_arg[0]” • read 1st 8 bytes into u.u_arg[0] (u_base=u_uarg[0]) • readi() w0: file type, w1/w2/w3: sizes of each segments • 3095 check file type (pure code - separate text & data?)
a.out format w0 w1 8 bytes magic number text size data size bss size symbol table size entry point relo. info. w2 w3 “Magic number” first few bytes of executable file Binary Standard “COFF & ELF” (trend – ELF supports dynamic libraries) Script If first 2 bytes is # ! – pathname of interpreter (from BSD --- to Linux)
8 words • We just read 8 bytes from a.out • and we checked file type • We need to read body of a.out • First, need to allocate memory space • So find out the size of text, data segment • 3129 adjust memory space according to the finding • expand() • xalloc() is at 4433 and is for shared code • address of user space is now determined • 3138 estabur() again (save addresses in struct u) • prepare to read a.out file – read the rest of a.out • parameters for readi() • 3142 read data segment (i.e. main body of a.out) u.u_arg[]
Passing arguments to child process
EXECVE(2) Linux Programmer's Manual EXECVE(2) NAME execute program SYNOPSIS #include <unistd.h> int execve (const char*filename, char *constargv [], char *constenvp[]); DESCRIPTION execve() executes the program pointed to by filename must be either abinary executable, or a script starting with a line of the form "#! interpreter [arg]". …. argv is an array of argument strings passed to the new program. envp is an array of strings, conventionally of the form key=value, which are passed as environment to the new program. Both, argv and envp must be terminated by null pointer. The argument vector and environment can be accessed by the called program's main function, when it is defined as int main(int argc, char *argv[], char *envp[]). execve() does not return on success, and the text, data, bss, and stack of the calling process are overwritten by that of the program loaded. The program invoked inherits the calling process's PID, and any open file descriptors that are not set to close on exec. Parent passes arguments to child Child’s a.out (env_var_name, env_var_value) (child program will invoke exit() system call at termination -- compiler puts it)
cat ch1 ch2 fork() call newproc() newproc() copy proc copy image rerturn(0) exec() overlay wait() swtch() parent retire proc[0] child arise return(1) sh (parent) main() { If (PID=fork()) PID=0 exec () /*child*/ PID<>0 wait() /*parent*/ sh (child) main() { If (PID=fork()) PID=0 exec( ) /*child*/ PID<>0 wait() /*parent*/ sh sh cat ch1 ch2 cat ch1 ch2 10 3* child created (ready) 7* pop return address 12* exec() at 10 is done back to user mode
cat ch1 ch2 fork() call newproc() newproc() copy proc copy image rerturn(0) exec() overlay wait() swtch() parent retire proc[0] child arise return(1) sh (parent) main() { If (PID=fork()) PID=0 exec () /*child*/ PID<>0 wait() /*parent*/ cat (child) main() { Stack: sh cat Overlay 11 cat 3* child created (ready) 7* pop return address 12* exec() at 10 is done back to user mode
We just read a.out file from disk, Next, copy argv & envp (1) Parent sh forks child sh (2) Child sh calls exec() (3) exec() Kernel: struct user k-stack Kernel a.out copy to kernel buffer copy from kernel buffer struct user k-stack (0) cat argv a.out Parent sh copy argv, envp from child-sh’s user var to kernel buffer load cat a.out overwriting shell copy argv, envp from kernel buffer to child’s user stack <3-1> u--stack (1) argv envp <3-2> (2) (4) struct user k-stack struct user k-stack load overlay a.out cat a.out <3-3> Child sub-sh (3) u--stack u--stack cat
bp=getblk() • (4921) allocate a kernel buffer (from buffer cache) • 3049 Copy from caller to buffer (3049 3052 3059 ..) • Remaining sys call arguments, following pathname • (filename,argv, envp, ….) • 3049 cp points to actual kernel buffer (not buffer header) • 3052 ap = fuword(); 3058 c =fubyte(ap++); • 3059 for ( ) {c= fubyte(ap++); /* byte from parent’s image*/ • (*cp++ = c) /* exec() sys call arg into kernel buffer */ • until ( c = = 0) at 3067 • We must copy (argv, envp) into kernel buffer. • BEFORE loading new a.out image from disk. • because current user image is being overwritten during loading
Now new image has been read from disk (at 3142) • 3153 Copy from buffer to new user process stack • 3153 cp points to actual kernel buffer (not header) • 3161 while { • 3161 subyte(c++, *cp); /* store byte into user space */ • Eventually, the system call arguments are moved • from previous user image • kernel buffer • new image’s user stack
Algorithm of exec() • load a.out from disk (overlay) • init user stack for main (path, argv, envp) • Start from main() Copy argv, envp from sub-shell to kernel (first, allocate kernel buffer, then copy) Copy them from kernel to new a.out’s user stack (as arguments to main()) ... Yes .. it is like an IPC .. kernel in between Read new a.out (Overlay old a.out)
1. Makes proc[0] then call newproc() to make proc #1 2. Alloc & copy proc[1] alloc & copy image for proc #1 10. swtch() becomes proc #0 which selects next job retu (sh) 4. Return(0) from newproc() Call sched() .. swtch() saveu() main() proc 0 8. init creates sh for terminals 9. swtch() proc 1 7. exec init, i.e. load & run init 3. Proc #1 is created & is in Ready state 6. Copy array to user space & jump 11. sh runs sh is created ready queue (Continued) sh 5. retu(), return(1) from newproc() to main
User types “cat” at shell prompt … Parent invokes wait() sys call Creates child fork() 7. Returns from swtch() 1. Calls swtch() 5 End of cat code exit () sys call swtch() sh (parent) 2. scheduler proc #0 3. within swtch() calls retu (child) child is dispatched 6. scheduler chooses parent sh retu (sh) child-sh is created & is in Ready state cat (child) 4. child returns from swtch() returns from fork() child-sh exec(“bin/cat) User mode Kernel mode
Creates child fork() 6 Returns from swtch() 1. Calls swtch() sh (parent) 4 calls swtch() 2. scheduler proc #0 3. cat dispatched 5. scheduler cat (child) Coroutine Calls: sh cat swtch() swtch() User mode Kernel mode cat sh
Creates child cat 6 Returns from swtch() 1. Calls swtch() sh (parent) 4 calls swtch() 2. scheduler proc #0 3. cat dispatched 5. scheduler cat (child) Retiring sh cat OS sched swtch() swtch() User mode Kernel mode Arising cat sh
Fork() creates child Later exit() syscall calls swtch() wait() sys call which calls swtch() sh (parent) 2. scheduler proc #0 3. Child is dispatched cat (child) sh exec cat child: main(argv ) { finds out argv= cat any fork() exec(cat) pass “any” as argv to Pcat } User mode Kernel mode
fork() call newproc() newproc() copy proc copy image rerturn(0) exec() overlay wait() swtch() parent retire proc[0] child arise return(1) sh (parent) main() { If (PID=fork()) PID=0 exec () /*child*/ PID<>0 wait() /*parent*/ sh (child) main() { If (PID=fork()) PID=0 exec () /*child*/ PID<>0 wait() /*parent*/ Stack: overlay new a.out (child) main() exit() 2 9 sh 5 1 3* 4 10 6 8 entered newproc() entered fork() 11 12* 6* 3* child created (ready) 7* pop return address 12* exec() at 10 is done back to user mode cat 13 7*