Process 可簡單地劃分成處於這三種狀態
100%, since the chance of instruction using CPU is both 100%; therefore, no IO will happen.
11 time units, "4:100,1:0" the first process will take up 4 time units, and the second process will take up 7 time units (1 for calling I/O (initialization), 5 (default) for doing I/O, and 1 for completion) (These are all used for simplicity, not realistic though)
Yes, since we are running the I/O part first and it is blocked during I/O query, in the meantime, the other process can run its instructions; afterward, the CPU control returns to the first process for I/O completion.
The time units will increase to 11. Since the other process cannot take over the control of CPU (because the flag is set to SWITCH_ON_END) when the first process is blocked.
The time units fall back to 7. Since the other process can do work when the other is blocked (doing I/O).
The first process delayed its io_done which causes its next I/O to be runned after all the other process had finished which is not very efficient. No, the CPU is not being effectively utilized.
Since I/O done is not delayed, the OS can effectively schedule the processes, i.e. run I/O contiguously and when the process is blocked, it switch to the other process.
It differs from the last scenario since IO done is waitng for the other processes to complete and then executes it which postpones the subsequent IO execution, and in turn increases the overall run time.
The reason is that we dont know if we still get IO to do subsequently; therefore, postponing the io_done will cause subsequent IO actions to be waiting for the first IO action to be completed which is not ideal for such case, the better solution would be IO running consecutively and other process be executed when the IO process is blocked.
(each generation might vary)
As fork_percentage
goes higher, the process tree would more likely be nested, vice versa.
(each generation might vary)
Without the -R
flag, the child process of c (d and e) would become the child process of a. However, with he -R
flag, process d and e would now become the child process of b.
When a parent process exits (either terminates or crashes unexpectedly) and leaves its child processes still running, its child processes are called "Orphan Process".
Or, when a parent process completes execution before the child process finishes, the child process is also called as "Orphan Process".
Inside Linux, the kernel will identify such situations and attach or reassign the "orphan process" the the "init" process, which is a process with PID 1 and is the root of all processes in Linux kernel (it always keep running to ensure system stability). The init process will ensure the orphaned process to complete and release its memory, remove from process list, etc.
(each generation might vary)
(each generation might vary)
Write a program that calls fork(). Before calling fork(), have the main process access a variable (e.g., x) and set its value to something (e.g., 100). What value is the variable in the child process? What happens to the variable when both the child and parent change the value of x?
Below is the C program i am working on.
This program outputs:
Which is expected. (except for the fact that sometimes but rarely child process will get executed first and reverse the ordering of the messages. this can be implemented by adding the wait() system call at the parent process path)
However, what if I try to comment out x = 10
inside the child process path?
Well, it now gets interesting! I got the following output.
Though the parent process ran first, the value of x inside the child process seems to remain with the value of 1.
Let us comment out x = 5
to see what happens.
It is somewhat expected since the parent ran first and didn't change the value of x, and child process changed it so x became 10 in child process.
What if we let the parent process wait for the child process to finish?
I ran the program again, here's what I got.
Now, it seems like the child process and the parent process do really have separate address space. No matter how i modify the program, they don't share the same x
variable. This answers the question, What happens to the variable when both the child and parent change the value of x?
– they each have their own copy of x, and changes in one do not affect the other.
Write a program that opens a file (with the open() system call) and then calls fork()to create a new process. Can both the child and parent access the file descriptor returned by open()? What happens when they are writing to the file concurrently, i.e., at the same time?
Below is a program that opens a file using the open() syscall, calls fork(), writes something to the file, and closes the file. Both child and parent process can access the file descriptor returned by open(). When these two process run concurrently, there is no guarantee which process will run first, but usually the parent process will run first.
File descriptor is an unique identifier (integer) in the process that refers to the file description in the kernel. A file descriptor can be used to access files, pipes, or network sockets.
11.1.1 Streams and File Descriptors
When you want to do input or output to a file, you have a choice of two basic mechanisms for representing the connection between your program and the file: file descriptors and streams. File descriptors are represented as objects of type int, while streams are represented as FILE * objects.File descriptors provide a primitive, low-level interface to input and output operations. Both file descriptors and streams can represent a connection to a device (such as a terminal), or a pipe or socket for communicating with another process, as well as a normal file. But, if you want to do control operations that are specific to a particular kind of device, you must use a file descriptor; there are no facilities to use streams in this way. You must also use file descriptors if your program needs to do input or output in special modes, such as nonblocking (or polled) input (see File Status Flags).
Streams provide a higher-level interface, layered on top of the primitive file descriptor facilities. The stream interface treats all kinds of files pretty much alike—the sole exception being the three styles of buffering that you can choose (see Stream Buffering).
The main advantage of using the stream interface is that the set of functions for performing actual input and output operations (as opposed to control operations) on streams is much richer and more powerful than the corresponding facilities for file descriptors. The file descriptor interface provides only simple functions for transferring blocks of characters, but the stream interface also provides powerful formatted input and output functions (printf and scanf) as well as functions for character- and line-oriented input and output.
Since streams are implemented in terms of file descriptors, you can extract the file descriptor from a stream and perform low-level operations directly on the file descriptor. You can also initially open a connection as a file descriptor and then make a stream associated with that file descriptor.
In general, you should stick with using streams rather than file descriptors, unless there is some specific operation you want to do that can only be done on a file descriptor. If you are a beginning programmer and aren’t sure what functions to use, we suggest that you concentrate on the formatted input functions (see Formatted Input) and formatted output functions (see Formatted Output).
If you are concerned about portability of your programs to systems other than GNU, you should also be aware that file descriptors are not as portable as streams. You can expect any system running ISO C to support streams, but non-GNU systems may not support file descriptors at all, or may only implement a subset of the GNU functions that operate on file descriptors. Most of the file descriptor functions in the GNU C Library are included in the POSIX.1 standard, however.
There are 5 I/O system calls: creat(), open(), close(), read(), and write().
Write another program using fork(). The child process should print “hello”; the parent process should print “goodbye”. You should try to ensure that the child process always prints first; can you do this without calling wait() in the parent?
My first thought was to add the sleep() function, but this is quite unusable since you cannot predict how long would it will take for another process to complete.
When reading the manuals for wait() system call, I came across a system call, waitpid(), which allows you to specify which child process to wait for. Additionally, there is another system call waitid() that can store signal information.
Alternatively, we can use a shared flag between processes by leveraging mmap(). By adding the MAP_SHARED
flag, the memory region is visible to both the parent and child processes, allowing them to share the same variable.
Write a program that calls fork() and then calls some form of exec() to run the program /bin/ls. See if you can try all of the variants of exec(), including (on Linux) execl(), execle(), execlp(), execv(), execvp(), and execvpe(). Why do you think there are so many variants of the same basic call?
From the manual, the family of exec() is build on top of the execve() system call.
execve() executes the program referred to by pathname. This causes the program that is currently being run by the calling process to be replaced with a new program, with newly initialized stack, heap, and (initialized and uninitialized) data segments.
execve() does not return on success, and the text, initialized data, uninitialized data (bss), and stack of the calling process are overwritten according to the contents of the newly loaded program.
If the current program is being ptraced, a SIGTRAP signal is sent to it after a successful execve().
The exec() family
From wikipeida:
The base of each is exec (execute), followed by one or more letters:
e – Environment variables are passed as an array of pointers to null-terminated strings of form name=value. The final element of the array must be a null pointer.
l – Command-line arguments are passed as individual pointers to null-terminated strings. The last argument must be a null pointer.
p – Uses the PATH environment variable to find the file named in the file argument to be executed.
v – Command-line arguments are passed as an array (vector) of pointers to null-terminated strings. The final element of the array must be a null pointer.
execl()
For some reasons, i have to add an empty string (or sth else like "idk") at arg[0].
execlp()
execle()
execv()
execvp()
execvpe()
The reason why we need six variants of exec() is that we have different use cases. The exec() family has l
, e
, v
, and p
suffixes. l
stands for list, e
stands for Environment, v
stands for Vector, and p
stands for Path. Sometimes you might need to parse the user input as variable arguments or a vector and you will use execl() or execv(), and sometimes you might need to change the environment variables to make it differ from the parent process and you might use execve(), and lastly, on different systems, the program may not be in the same location across systems, so execvp() helps search in PATH.
Now write a program that uses wait() to wait for the child process to finish in the parent. What does wait() return? What happens if you use wait() in the child?
The following program does what the question stated. When i run the program, it executes with no errors. The wait() system call returns the PID of the terminated child process.
I then used waitpid() to let the parent and child process wait for each other. Surprisingly, it doesn't end up to be a infinite loop. It executes in a flash. I wonder how this make sense.
Write a slight modification of the previous program, this time using waitpid() instead of wait(). When would waitpid() be useful?
I did it before this is mentioned before this is on the next page (hurray). waitpid() gets useful when you need to wait for a specific process, not just the child process.
Write a program that creates a child process, and then in the child closes standard output (STDOUT FILENO). What happens if the child calls printf() to print some output after closing the descriptor?
When the child process calls printf() after closing the descriptor, it doesn't output the child path: Sup.
message.
Write a program that creates two children, and connects the standard output of one to the standard input of the other, using the pipe() system call.
How can the operating system regain control of the CPU so that it can
switch between processes?
A Cooperative Approach: Wait For System Calls
A Non-Cooperative Approach: The OS Takes Control
Saving and Restoring Context
ASIDE: KEY CPU VIRTUALIZATION TERMS (MECHANISMS)
Measure the costs of a system call
Measure the costs of a context switch
Workload Assumptions
We have the following (unrealistic) assumptions about each running process (sometimes called job):
Scheduling Metrics
First In, First Out (FIFO)
Relax Assumption 1
(Each job runs for the same amount of time.)