HUST Operating System Lab2: Process&Thread Synchronization Mechanisms and Application Programming
Operating System Lab2
Experiment Objectives
- Understand the concepts and application programming process of processes/threads;
- Understand the synchronization mechanisms and application programming of processes/threads;
- Master and promote domestic operating systems (recommend Galaxy Kylin or Ubuntu Kylin)
Experiment Content
- Create 2 threads A and B in Linux/Windows to loop output data or strings.
- Create (fork) a child process in Linux, experiment with wait/exit functions
- Use threads to implement concurrent drawing of circles and squares in Windows/Linux.
- Use threads to implement “producer-consumer” synchronization control in Windows or Linux
- Use signal mechanism (signal) to implement inter-process communication in Linux
- Simulate dining philosophers in Windows or Linux, provide deadlock and non-deadlock solutions.
- Study Linux kernel and use printk to debug process creation and scheduling policy related information.
Task 1: Create 2 Threads A and B in Linux/Windows to Loop Output Data or Strings
Requirements:
Use pthread thread library or CreateThread function
Thread A outputs 1-1000 in ascending order; Thread B outputs 1000-1 in descending order. To avoid output being too fast, output one number every 0.2 seconds (adjustable).
When outputting data, also output “A” or “B” to indicate which thread is outputting, and pay attention to formatted output information. For example:
1
2
3
4
5A:1000
A:0999
B:0001
A:0998
B:0002
Write Code and Compile
1 |
|
Detailed explanation of pthread_create function:
Use the command gcc -o mission1 mission1.c -lpthread to
compile the file and execute it.
View Process Running Status During Execution
Common commands for viewing processes or threads:
1 | ps -ef | grep [process name] # View information of processes with specific names |
After running mission1, use ps command to display its detailed information
In the figure, SPID represents the thread ID numbers, 3852 and 3852 are the A and B threads we created
Program Execution Results
Reference Materials:
Task 2: Create (fork) a Child Process in Linux, Experiment with wait/exit Functions
Requirements:
- Effect 1: Parent process does not use wait function, let parent
process end before child process, child process enters infinite loop or
long-term loop, observe process ID and parent process ID of parent and
child processes.
- Use printf in the program to output process number and parent process number of each process. Note, parent process and child process output should provide corresponding prompt strings for mutual distinction, same below
- At the same time, use ps command to display process list, observe process ID and parent process ID of specified processes, and explain whether these IDs are consistent with those output by printf.
- Effect 2: Parent process uses wait function. Child process sleeps for 5 seconds, parent process does not sleep. Child process uses exit to return parameters. Parent process printf the parameters returned by child process.
Effect One
The core of this task is to let the parent process end before the child process, observing the changes in process IDs.
1 |
|
Program execution result is as follows:
Parent process PID=3604, child process PID=3605, then after the parent process ends after 20s, the child process prints its own PID and parent process PID. Within the 20s before the parent process ends, the child process prints parent process PID=3604, but after the parent process ends, the child process continues running, and at this time the parent process ID becomes 1. The reason for this effect is:
Effect Two
Child process ends before parent process, and uses exit to return a value, parent process prints this status value.
1 |
|
Program execution result is as follows:
In the code, child process exit(42), parent process prints the exit status code 42.
【Linux】——Process Creation fork() Detailed Explanation
Task 3: Use Threads to Implement Concurrent Drawing of Circles and Squares in Windows/Linux
For this task, I used Qt6 to implement a dual-threaded GUI interface for drawing circles and squares. Here I only explain the core code parts:
Since Qt’s drawing actions can only be completed in the main thread, our two threads for drawing circles and squares are used to calculate the coordinate points of circles and squares respectively, and pass them to the main thread, which completes the corresponding drawing actions.
Project file directory is as follows:
First, Qt needs to overwrite paintEvent in the MainWindow class to draw graphics:
1 | void MainWindow::paintEvent(QPaintEvent *event) { |
Two thread-related classes, one is a circle thread class, one is a square thread class, they are both subclasses of Qt’s thread class QThread. Since they only differ in coordinate point calculation, I’ll choose the circle thread class to explain:
The process function in the circle thread class is used to handle the next coordinate point, then emit the circlePoint signal, passing this coordinate to the main thread for processing.
1 | void CircleThread::process() { |
For the circle thread class, we need to overwrite the run function. The run function is called when the thread starts. The specific logic is to call the process function at intervals. The process function handles point information and then sends it to the main thread.
1 | void CircleThread::run() { |
What the main thread needs to do is initialize an instance of the circle thread class and connect the circlePoint signal mentioned earlier with the slot that handles points. Note that the Slot function is written as a Lambda expression, adding the points returned from the child thread to the circle point set, then updating the drawing.
1 | // Create circle drawing thread |
The final step is relatively simple. We need to start the threads, and thread startup is controlled by the “Start Drawing” button. Clicking it starts both threads.
1 | // This is a slot function used to connect with button's click signal |
Finally, start the program. The program execution effect is as follows:
Reference Materials:
Qt Multi-threading Method 1 (Step-by-step explanation + code + demonstration)
Task 4: Use Threads to Implement “Producer-Consumer” Synchronization Control in Windows or Linux
Task Requirements:
- Use an array (10 elements) instead of a buffer. 2 input threads produce products (random numbers) and store them in the array; 3 output threads take numbers from the array and output them.
- Linux uses mutex objects and lightweight semaphore objects, main functions: sem_wait(), sem_post(), pthread_mutex_lock(), pthread_mutex_unlock()
- Producer 1 data: 1000-1999 (random interval 100ms-1s for each data), Producer 2 data: 2000-2999 (random interval 100ms-1s for each data)
- Consumers sleep for random time 100ms-1s to consume one data.
- Screen print (or log file record) production and consumption records for each data.
Source code is as follows:
1 |
|
Code Analysis:
First, we should add a mutex lock to the product area to prevent simultaneous access by multiple threads. Then we need to ensure that when the product buffer is full, producers stop producing, and when there are no products, consumers cannot consume. So we need P-V operations to complete the synchronization mechanism. The empty signal represents the current empty slots in the buffer. Whenever a producer starts producing, empty slots decrease by 1; after a consumer consumes, empty slots increase by 1. The full signal represents the current number of products in the buffer. When a consumer starts consuming, it decreases by 1; when a producer finishes producing, it increases by 1. This implements the synchronization mechanism between producers and consumers.
1 | /*Producer*/ |
Program execution result is as follows:
Use ps command to view all threads in the current process:
Reference Materials:
Thread Synchronization Problem - Producer Consumer
Task 5: Use Signal Mechanism (signal) to Implement Inter-Process Communication in Linux
Task Requirements:
- Parent process creates (fork) child process and makes child process enter infinite loop.
- Child process outputs “I am Child Process, alive !” every 2 seconds
- Parent process asks user “To terminate Child Process. Yes or No? ” requiring user to answer Y or N from keyboard. If user answers N, delay 2 seconds before asking again.
- If user answers Y, send user signal to child process to make it end.
- Before child process ends, print string: “Bye,World !”
- Functions: kill(), signal(), use user signal, write signal handler function
The core of this task is to use the kill function to terminate the child process, and pass a signal SIGUSR1 when killing (user-defined signal that can be used to report abnormal behavior such as division by zero errors, segmentation faults, etc., or to control processes such as terminating processes, stopping (pausing) processes, continuing (resuming) stopped processes, etc.). The child process receives the signal and calls the signalHandler signal processing function to perform corresponding operations.
Source code:
1 |
|
Code execution result:
Reference Materials:
Linux Inter-Process Communication Lecture 3 Signal signal kill
Task 6: Simulate Dining Philosophers in Windows or Linux, Provide Deadlock and Non-Deadlock Solutions
Task Requirements:
- Provide both solutions that may cause deadlock and solutions that cannot cause deadlock.
- For solutions that may cause deadlock, refer to course materials. Windows try using critical section objects (EnterCriticalSection, LeaveCriticalSection); Linux try using mutex locks (pthread_mutex_lock, pthread_mutex_unlock)
- Solutions that absolutely cannot cause deadlock, for example: try to pick up both chopsticks, if both can be picked up then pick them up, otherwise don’t pick up either.
- Linux try mutex functions pthread_mutex_lock, pthread_mutex_trylock, etc.
- To enhance randomness, maintain random duration of 100ms-500ms between states.
- [Optional] Graphical interface showing philosophers picking up chopsticks, eating, putting down chopsticks, thinking, etc.
Deadlock Solution
The problem with the deadlock solution is that each philosopher picks up the left chopstick first, then the right chopstick. When they all pick up the left chopstick simultaneously, no philosopher can get the right chopstick, creating a deadlock problem.
1 |
|
Deadlock state:
It can be observed that they all picked up the left chopsticks simultaneously, creating a deadlock phenomenon.
Non-Deadlock Solution
My solution to deadlock is that philosophers try to pick up the left chopstick first, then the right chopstick. Unlike the above, if philosophers cannot get the right chopstick to eat, they put down the left chopstick they currently hold, thus avoiding the deadlock problem.
In terms of specific functions: pthread_mutex_trylock function replaces pthread_mutex_lock to avoid blocking. If the lock cannot be acquired immediately, it won’t enter a waiting state, thus avoiding deadlock situations.
1 |
|
Non-deadlock program execution result:
Here I intentionally made philosophers sleep for a longer time after getting the left chopstick, creating a conflict where they all get the left chopstick simultaneously. From the figure, we can see that all 5 philosophers indeed picked up the left chopsticks simultaneously. Unlike the deadlock approach, philosopher 4 actively put down the left chopstick and re-entered the thinking state, avoiding the deadlock situation.
Reference Materials:
Five Philosophers Dining Problem
Task 7: Study Linux Kernel and Use printk to Debug Process Creation and Scheduling Policy Related Information
Requirements: Write application Hello.c, call fork to create process, track the fork process of the newly created child process in the kernel and display PCB member variables related to scheduling policy.
- Write application Hello.c, call fork to create child process (functionality unlimited), print parent and child process ID numbers.
- Use printk in appropriate locations in the kernel (such as somewhere in the do_fork function) to output debugging information like “currently creating process corresponding cmd, process ID and parent process ID”.
- To avoid frequent output of the above debugging information by the do_fork function, it must be limited to only output the above debugging information when fork is called in the Hello program. Please think about how to implement this.
Reference method: Kernel design global variable bool flag and system call SetDebug(bool), SetDebug can modify flag value to true or false. In the Hello program, call SetDebug(true) before calling fork function and SetDebug(false) after calling fork function to modify flag. When printk debugging information, check flag to determine whether to use printk to output debugging information.
Friendly reminder: The do_fork function has been replaced by the kernel_clone function in newer versions of Linux kernel source code.
Modify ./kernel/fork.c file
Use printk in kernel_clone to print related information. p is a pointer to the task_struct structure, comm is the currently executing command, pid is the child process ID, current->pid is the parent process ID, current is a macro pointing to the parent process.
Add new system call setdebug for setting debug_fork_flag. The bool value debug_fork_flag is a global variable used to control whether to output.
Add Declarations
- System call: ./kernel/fork.c (already modified)
- System call function declaration ./include/linux/syscalls.h
- ID: ./arch/x86/entry/syscalls/syscall_64.tbl
- ID declaration: ./include/uapi/asm-generic/unistd.h
The modification operations for the remaining 3 files have been practiced in the task of adding system calls in experiment 1, so I won’t repeat them here.
Compile Kernel
1 | # Already practiced in experiment 1 kernel compilation |
Write Test Code
1 |
|
To test the functionality, I commented out the SetDebug function in another test code and executed both programs simultaneously. The expected effect is that only the program that called the SetDebug function will output debug information during fork.
test1 did not enable print information, test2 enabled print information.
Use dmesg to view information. In the figure below, we can see a line in the background information about a process being created, cmd=test2, and comparing with the child process and parent process PIDs in the above figure, they are also consistent.
Reference Materials:
Linux Kernel Process - do_fork() Function Implementation Principle







