Lecture 3
Lecture 3
CIS 5050
Software Systems
2
©2016-2024 Linh Thi Xuan Phan
Plan for today
• System calls
– What are they, and why do we need them? NEXT
3
©2016-2024 Linh Thi Xuan Phan
User mode vs. kernel mode
"User level"
(unprivileged
code)
Privileged
PCB
PCB
PCB kernel code
Kernel
5
©2016-2024 Linh Thi Xuan Phan
Recall: Function calls in user level
int foo(int arg) main: foo:
{ ... mov 8(esp), eax
return arg+3; push 5 add eax, 3
} sub esp, 4 mov eax, 4(esp)
call foo ret
int main(void) pop edx (return addr)
{ add esp, 4 8
(empty) Stack
int b = foo(5); ... frame
5
return b;
} Stack
Source code
Address space
10
©2016-2024 Linh Thi Xuan Phan
System call steps: Kernel entry
Process (PID 1) Kernel
foo: read: entry: sys_read:
... mov 4(esp),ebx push ... ...
push 7 mov 8(esp),ecx cmp eax, 3 pop ...
push 200 mov 12(esp),edx je sys_read sysexit
push bufaddr mov eax, 3 (addr of 'call') cmp eax, 4
call read sysenter bufaddr ...
add esp, 12 ret
200
...
void foo() {
7 PID: 1
UID: 47
PID: 2
UID: 38
...
...
read(7, &buf, 200);
Stack AS: 4711
...
AS: 0815
...
...
}
12
©2016-2024 Linh Thi Xuan Phan
System call steps: Kernel exit
Process (PID 1) Kernel
foo: read: entry: sys_read:
... mov 4(esp),ebx push ... ...
push 7 mov 8(esp),ecx cmp eax, 3 pop ...
push 200 mov 12(esp),edx je sys_read sysexit
push bufaddr mov eax, 3 (addr of 'call') cmp eax, 4
call read sysenter bufaddr ...
add esp, 12 ret
200
...
7 PID: 1
UID: 47
PID: 2
UID: 38
...
Stack AS: 4711
...
AS: 0815
...
14
©2016-2024 Linh Thi Xuan Phan
Blocking
• Sometimes the system call can't make progress
– ... at least not right away (e.g., read() called but no data available)
• In this case, the kernel runs another process
– Idea: Use the time effectively while the original process waits
– If there are multiple runnable processes, which one should it pick?
• The original process is said to be blocked
15
©2016-2024 Linh Thi Xuan Phan
Context switch
Process (PID 1)
Process 2) Kernel
foo:
bar: wait:
read: entry: sys_read:
sys_wait:
...
... mov eax,
4(esp),ebx
114 push ... ...
push
call 7wait sysenter
mov 8(esp),ecx cmp eax, 3 pop ...
push
... 200 ret
mov 12(esp),edx je sys_read sysexit
push bufaddr mov eax, 3 (addr of 'call') cmp eax, 4
call read sysenter bufaddr ...
add esp, 12 ret
200
... (addr 7
of 'call') PID: 1
UID: 47
PID: 2
UID: 38
...
Stack AS: 4711
...
AS: 0815
...
17
©2016-2024 Linh Thi Xuan Phan
Context switching and efficiency
• Switching between user and kernel is expensive
– Need to save and restore state, etc.
– Far more expensive than a function call!!
20
©2016-2024 Linh Thi Xuan Phan
Plan for today
• System calls
– What are they, and why do we need them?
– Kernel entry and exit
– Blocking and context switching
– Some common system calls NEXT
– The kernel's perspective
21
©2016-2024 Linh Thi Xuan Phan
Example: The File API
• UNIX I/O is (mostly) based on a streaming model
– Data is seen as a stream of bytes
22
©2016-2024 Linh Thi Xuan Phan
File descriptors
• A process can have multiple I/O streams open at
any given time
• Processes use file descriptors to tell the kernel
which stream they are referring to
– System calls like open(), pipe(), ... return new file descriptors
– System calls like read(), write(), ... take them as arguments
– Internally, this is just a number; it can be thought of as an index into
the kernel's file descriptor table
0 stdin
• The shell gives processes some 1 stdout
standard streams: 2 stderr
3
– STDIN: Standard input
4
– STDOUT: Standard output 5
– STDERR: Standard error
...
– Usually associated with a terminal (but can be redirected) 23
©2016-2024 Linh Thi Xuan Phan
Opening and closing streams
• int open(path, flags, [mode])
– Opens a file for reading and writing
• It is possible to open the same file more than once! (How would that be useful?)
– Flags can say whether the file will be read, written to, or both; it can
request that the file be created if it doesn't exist yet, it can request
that read/write operations should not block, etc.
• When creating a new file, the third argument is required
– If all goes well, returns a new file descriptor; otherwise returns -1
• int close(int fd)
– Closes an open file descriptor
• int pipe(int fd[2])
– Creates a new pipe and returns two file descriptors: one for the read
end and one for the write end
• Different calls for other stream types
– Example: connect(). More about this later! 24
©2016-2024 Linh Thi Xuan Phan
Reading from & writing to streams
• int read(int fd, void *buf, size_t size)
– Asks the kernel to read up to 'size' bytes and write them to buf
– If successful, returns the number of bytes actually read
• This can be less than 'size' (under what conditions?)
• It can even be zero! (when?)
– Only guaranteed to read 'size' bytes if the descriptor a) belongs to a
file, and b) has at least that many bytes left
28
©2016-2024 Linh Thi Xuan Phan
Plan for today
• System calls
– What are they, and why do we need them?
– Kernel entry and exit
– Blocking and context switching
– Some common system calls
– The kernel's perspective NEXT
29
©2016-2024 Linh Thi Xuan Phan
How to implement system calls?
• So far, we've mostly looked at system calls from
the user-level perspective
– How processes use the calls, what the calls do for the process, etc.
30
©2016-2024 Linh Thi Xuan Phan
How to implement: fork()
• What does the kernel do to implement fork()?
1. Allocate a new PCB for the child
2. Copy (most of) the values from the parent's PCB to the child's
• Including file descriptors, which are inherited from the parent
3. Create a new address space for the child
4. "Copy" the memory contents from parent to child
• In practice, this is usually implemented using copy-on-write (CoW)
5. Mark parent and child as ready to run
6. Set the return values
• Parent returns the child's PID; child returns zero
7. Return to user space
• To the parent, or to the child, or to whichever other process the dispatcher picks
31
©2016-2024 Linh Thi Xuan Phan
How to implement: exec()
• What does the kernel do to implement exec()?
1. Open the binary that the caller specified
2. Clear the address space
3. Load (or map) contents from the binary into memory
• This might take a while (I/O required!), so the process might block
4. Reset the context
5. Mark the process as ready
32
©2016-2024 Linh Thi Xuan Phan
How to implement: wait()
• What does the kernel do to implement wait()?
1. Check whether any child processes have terminated
2. If so:
1. Extract the exit code from the (zombie) PCB
2. Free the child's PCB
3. Set the parent's return value to the exit code
4. Mark the parent as ready, and return
3. If not, mark the parent as blocked
33
©2016-2024 Linh Thi Xuan Phan
How to implement: exit()
• What does the kernel do to implement exit()?
1. Set the process's state to terminated
2. Release any resources (address space, etc.)
3. If the parent is blocked in wait(), change the parent's state to ready
34
©2016-2024 Linh Thi Xuan Phan
How to implement: read()
• What does the kernel do to implement read()?
1. Check for errors (file descriptor is invalid, etc.)
• If so, mark process as ready and return an error code
• The user-level library (glibc?) will put the error code into 'errno' and return -1
from the wrapper function
2. Check whether data is available for reading
• Recall that read() can read from many types of streams, so the process for this
varies with the stream type
3. If data is already available in kernel memory:
1. Pick N:=min(buffer size, number of available bytes)
2. Copy N bytes to the specified user-level buffer in the process
3. Set return value to N and mark the process as ready
4. If data is not available but can be read (e.g., from disk):
1. Pick N as above, send read request to the device, mark process as blocked
5. If no data is available:
1. Check if the file descriptor has been set to nonblocking; if so, return 0
2. Otherwise, mark the process as blocked
35
©2016-2024 Linh Thi Xuan Phan