操作系统实验2
实验目标
理解进程/线程的概念及应用编程过程;
理解进程/线程的同步机制及应用编程;
掌握并推广国内操作系统(推荐银河麒麟或Ubuntu Kylin)
实验内容
在Linux/Windows中创建2个线程A和B,循环输出数据或字符串。
在Linux中创建(fork)一个子进程,实验wait/exit函数。
使用线程在Windows/Linux中实现圆形和方形的并发绘制。
使用线程在Windows或Linux中实现“生产者-消费者”同步控制。
使用信号机制(signal)在Linux中实现进程间通信。
在Windows或Linux中模拟哲学家就餐,提供死锁和非死锁解决方案。
学习Linux内核并使用printk调试进程创建和调度策略相关信息。
任务1:在Linux/Windows中创建2个线程A和B,循环输出数据或字符串
要求:
使用pthread线程库或CreateThread函数。
线程A按升序输出1-1000;线程B按降序输出1000-1。为了避免输出过快,每0.2秒输出一个数字(可调)。
输出数据时,同时输出“A”或“B”以指示哪个线程在输出,并注意格式化输出信息。例如:
1 2 3 4 5 A:1000 A:0999 B:0001 A:0998 B:0002
编写代码并编译
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #include <stdio.h> #include <pthread.h> #include <unistd.h> void * print_numbers_ascending (void * arg) { for (int i = 1 ; i <= 1000 ; i++) { printf ("A:%04d\n" , i); usleep(200000 ); } return NULL ; } void * print_numbers_descending (void * arg) { for (int i = 1000 ; i >= 1 ; i--) { printf ("B:%04d\n" , i); usleep(200000 ); } return NULL ; } int main () { pthread_t threadA, threadB; pthread_create(&threadA, NULL , print_numbers_ascending, NULL ); pthread_create(&threadB, NULL , print_numbers_descending, NULL ); pthread_join(threadA, NULL ); pthread_join(threadB, NULL ); return 0 ; }
pthread_create函数的详细解释:
pthread_create函数详细解释
使用命令gcc -o mission1 mission1.c -lpthread编译文件并执行。
查看执行过程中的进程运行状态
查看进程或线程的常用命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ps -ef | grep [进程名称] lixiang 4874 3403 0 11:11 pts/1 00:00:00 ./mission1 lixiang 4883 3455 0 11:12 pts/2 00:00:00 grep --color=auto mission1 ps -T -p [进程PID] PID SPID TTY TIME CMD 4874 4874 pts/1 00:00:00 mission1 4874 4875 pts/1 00:00:00 mission1 4874 4876 pts/1 00:00:00 mission1 ps -Tfl -p [进程PID] F S UID PID SPID PPID C PRI NI ADDR SZ WCHAN STIME TTY TIME CMD 0 S lixiang 4945 4945 3403 0 80 0 - 21144 futex_ 11:17 pts/1 00:00:00 ./mission1 1 S lixiang 4945 4946 3403 0 80 0 - 21144 hrtime 11:17 pts/1 00:00:00 ./mission1 1 S lixiang 4945 4947 3403 0 80 0 - 21144 hrtime 11:17 pts/1 00:00:00 ./mission1 -e: 显示所有进程,而不仅仅是当前用户的进程。 -f: 显示完整的进程信息,包括父进程ID、CPU利用率等。 -l: 以长格式显示进程信息,包括进程状态、PID、终端、CPU利用率等。 -u user: 显示指定用户的进程信息。 -p pid: 显示指定PID的进程信息。 -s: 按进程启动时间排序输出。 -r: 按进程CPU利用率排序输出。 -T: 显示线程
运行mission1后,使用ps命令显示其详细信息。
mission1进程的详细信息
在图中,SPID表示线程ID号,3852和3852是我们创建的A和B线程。
程序执行结果
执行结果
参考资料:
Linux
- 线程创建
ps命令介绍
完整ps命令手册
任务2:在Linux中创建(fork)一个子进程,实验wait/exit函数
要求:
效果1:父进程不使用wait函数,让父进程在子进程之前结束,子进程进入无限循环或长期循环,观察父子进程的进程ID和父进程ID。
在程序中使用printf输出每个进程的进程号和父进程号。注意,父进程和子进程的输出应提供相应的提示字符串以便区分,以下同。
同时使用ps命令显示进程列表,观察指定进程的进程ID和父进程ID,并解释这些ID是否与printf输出的一致。
效果2:父进程使用wait函数。子进程睡眠5秒,父进程不睡眠。子进程使用exit返回参数。父进程printf子进程返回的参数。
效果一
本任务的核心是让父进程在子进程之前结束,观察进程ID的变化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <stdio.h> #include <unistd.h> #include <sys/types.h> int main () { pid_t pid = fork(); if (pid == 0 ) { while (1 ) { printf ("子进程输出:子进程PID:%d,父进程PID:%d\n" , getpid(), getppid()); sleep(5 ); } } else if (pid > 0 ) { printf ("父进程输出:父进程PID:%d,子进程PID:%d\n" , getpid(), pid); sleep(20 ); } else { printf ("创建子进程失败\n" ); } }
程序执行结果如下:
程序执行结果
父进程PID=3604,子进程PID=3605,然后在父进程结束后20秒,子进程打印自己的PID和父进程PID。在父进程结束的20秒内,子进程打印父进程PID=3604,但在父进程结束后,子进程继续运行,此时父进程ID变为1。造成这种效果的原因是:
父进程结束后子进程的父进程ID变为1的原因
效果二
子进程在父进程之前结束,并使用exit返回一个值,父进程打印这个状态值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <stdio.h> #include <unistd.h> #include <sys/wait.h> #include <stdlib.h> int main () { pid_t pid = fork(); if (pid == 0 ) { printf ("子进程输出:子进程PID:%d,父进程PID:%d\n" , getpid(), getppid()); sleep(5 ); exit (42 ); } else if (pid > 0 ) { int status; wait(&status); if (WIFEXITED(status)) { printf ("子进程退出状态:%d\n" , WEXITSTATUS(status)); } } else { printf ("创建子进程失败\n" ); } return 0 ; }
程序执行结果如下:
程序执行结果
在代码中,子进程exit(42),父进程打印退出状态码42。
【Linux】——进程创建fork()详细解释
任务3:使用线程实现Windows/Linux中的圆形和方形的并发绘制
对于这个任务,我使用Qt6实现了一个双线程的GUI界面,用于绘制圆形和方形。这里我只解释核心代码部分:
由于Qt的绘图操作只能在主线程中完成 ,因此我们用于绘制圆形和方形的两个线程分别用于计算圆形和方形的坐标点,并将其传递给主线程,由主线程完成相应的绘图操作。
项目文件目录如下:
项目文件目录
首先,Qt需要在MainWindow类中重写paintEvent以绘制图形:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 void MainWindow::paintEvent (QPaintEvent *event) { QPainter painter (this ) ; painter.setPen (Qt::blue); for (const QPoint &pt : circlePoints) { painter.drawPoint (pt); } if (circlePoints.size () > 1 ) { painter.setPen (Qt::red); painter.drawPolyline (circlePoints.constData (), circlePoints.size ()); } for (const QPoint &pt : squarePoints) { painter.drawPoint (pt); } if (squarePoints.size () > 1 ) { painter.setPen (Qt::blue); painter.drawPolyline (squarePoints.constData (), squarePoints.size ()); } }
两个与线程相关的类,一个是圆形线程类,一个是方形线程类,它们都是Qt的线程类QThread的子类。由于它们在坐标点计算上仅有差异,我将选择圆形线程类进行说明:
圆形线程类中的process函数用于处理下一个坐标点,然后发出circlePoint信号,将该坐标传递给主线程进行处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 void CircleThread::process () { double radianIncrement = 2 * (M_PI / 180.0 ); int centerX = center.x (); int centerY = center.y (); int x = centerX + static_cast <int >(radius * cos (circleAngle)); int y = centerY + static_cast <int >(radius * sin (circleAngle)); circleAngle += radianIncrement; emit circlePoint (QPoint(x, y)) ; }
对于圆形线程类,我们需要重写run函数。run函数在线程启动时被调用。具体逻辑是以间隔调用process函数,process函数处理点信息,然后将其发送到主线程。
1 2 3 4 5 6 7 void CircleThread::run () { while (!isInterruptionRequested ()) { process (); if (circleAngle >= 2 * M_PI) break ; msleep (62 ); } }
主线程需要做的是初始化圆形线程类的实例,并将之前提到的circlePoint信号与处理点的槽连接。注意,槽函数以Lambda表达式的形式编写,将子线程返回的点添加到圆形点集合中,然后更新绘图。
1 2 3 4 5 6 7 8 circleThread = new CircleThread (this , QPoint (width () / 4 , height () / 2 - height () / 10 ), qMin (width () / 2 , height ()) / 2 - 50 ); connect (circleThread, &CircleThread::circlePoint, this , [&](const QPoint &pt) { circlePoints.append (pt); update (); });
最后一步相对简单。我们需要启动线程,线程的启动由“开始绘制”按钮控制。点击它会启动两个线程。
1 2 3 4 5 6 7 void MainWindow::on_startpaint_clicked () { circleThread->start (); squareThread->start (); }
最后,启动程序。程序执行效果如下:
程序执行效果
参考资料:
Qt多线程方法1(逐步解释+代码+演示)
Qt文档
任务4:使用线程实现Windows或Linux中的“生产者-消费者”同步控制
任务要求:
使用一个数组(10个元素)代替缓冲区。2个输入线程生成产品(随机数)并存储在数组中;3个输出线程从数组中取出数字并输出。
Linux使用互斥对象和轻量级信号量对象,主要函数:sem_wait()、sem_post()、pthread_mutex_lock()、pthread_mutex_unlock()。
生产者1的数据:1000-1999(每个数据随机间隔100ms-1s),生产者2的数据:2000-2999(每个数据随机间隔100ms-1s)。
消费者随机睡眠100ms-1s以消费一个数据。
屏幕打印(或日志文件记录)每个数据的生产和消费记录。
源代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 #include <pthread.h> #include <semaphore.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #define BUFFER_SIZE 10 int buffer[BUFFER_SIZE];int in = 0 , out = 0 ;pthread_mutex_t mutex;sem_t empty, full;int random_sleep () { return rand() % 901 + 100 ; } void *producer (void *param) { int id = *(int *)param; int base = id == 1 ? 1000 : 2000 ; while (1 ) { int product = base + rand() % 1000 ; sem_wait(&empty); pthread_mutex_lock(&mutex); buffer[in] = product; in = (in + 1 ) % BUFFER_SIZE; printf ("生产者 %d 生产了 %d\n" , id, product); pthread_mutex_unlock(&mutex); sem_post(&full); usleep(random_sleep() * 1000 ); } return NULL ; } void *consumer (void *param) { int id = *(int *)param; while (1 ) { sem_wait(&full); pthread_mutex_lock(&mutex); int product = buffer[out]; out = (out + 1 ) % BUFFER_SIZE; printf ("消费者 %d 消费了 %d\n" , id, product); pthread_mutex_unlock(&mutex); sem_post(&empty); usleep(random_sleep() * 1000 ); } return NULL ; } int main () { pthread_t producers[2 ], consumers[3 ]; int producer_ids[2 ] = {1 , 2 }; int consumer_ids[3 ] = {1 , 2 , 3 }; pthread_mutex_init(&mutex, NULL ); sem_init(&empty, 0 , BUFFER_SIZE); sem_init(&full, 0 , 0 ); for (int i = 0 ; i < 2 ; ++i) { pthread_create(&producers[i], NULL , producer, &producer_ids[i]); } for (int i = 0 ; i < 3 ; ++i) { pthread_create(&consumers[i], NULL , consumer, &consumer_ids[i]); } for (int i = 0 ; i < 2 ; ++i) { pthread_join(producers[i], NULL ); } for (int i = 0 ; i < 3 ; ++i) { pthread_join(consumers[i], NULL ); } pthread_mutex_destroy(&mutex); sem_destroy(&empty); sem_destroy(&full); return 0 ; }
代码分析:
首先,我们应该在产品区添加互斥锁,以防止多个线程同时访问。然后我们需要确保当产品缓冲区满时,生产者停止生产;当没有产品时,消费者无法消费。因此我们需要P-V操作来完成同步机制。empty信号表示当前缓冲区中的空槽位。每当生产者开始生产时,空槽位减少1;消费者消费后,空槽位增加1。full信号表示当前缓冲区中的产品数量。当消费者开始消费时,减少1;当生产者完成生产时,增加1。这实现了生产者和消费者之间的同步机制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 sem_wait(&empty); pthread_mutex_lock(&mutex); buffer[in] = product; in = (in + 1 ) % BUFFER_SIZE; printf ("生产者 %d 生产了 %d\n" , id, product);pthread_mutex_unlock(&mutex); sem_post(&full); sem_wait(&full); pthread_mutex_lock(&mutex); int product = buffer[out];out = (out + 1 ) % BUFFER_SIZE; printf ("消费者 %d 消费了 %d\n" , id, product);pthread_mutex_unlock(&mutex); sem_post(&empty);
程序执行结果如下:
程序执行结果
使用ps命令查看当前进程中的所有线程:
使用ps命令查看当前进程中的所有线程
WCHAN解释
参考资料:
线程同步问题
- 生产者消费者
任务5:使用信号机制(signal)在Linux中实现进程间通信
任务要求:
父进程创建(fork)子进程,并使子进程进入无限循环。
子进程每2秒输出“I am Child Process, alive !”。
父进程询问用户“To terminate Child Process. Yes or No?
”,要求用户从键盘回答Y或N。如果用户回答N,延迟2秒后再询问。
如果用户回答Y,发送用户信号给子进程使其结束。
在子进程结束前,打印字符串:“Bye,World !”。
函数:kill()、signal(),使用用户信号,编写信号处理函数。
本任务的核心是使用kill函数终止子进程,并在杀死时传递信号SIGUSR1(用户定义的信号,可用于报告异常行为,如除零错误、段错误等,或控制进程,如终止进程、停止(暂停)进程、继续(恢复)停止的进程等)。子进程接收信号并调用signalHandler信号处理函数执行相应操作。
源代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <sys/types.h> #include <sys/wait.h> void signalHandler (int sig) { printf ("Bye, World!\n" ); exit (0 ); } int main () { pid_t pid = fork(); if (pid == 0 ) { signal(SIGUSR1, signalHandler); while (1 ) { printf ("I am Child Process, alive!\n" ); sleep(2 ); } } else if (pid > 0 ) { char answer; do { printf ("To terminate Child Process. Yes or No?\n" ); scanf (" %c" , &answer); if (answer == 'N' || answer == 'n' ) { sleep(2 ); } } while (answer != 'Y' && answer != 'y' ); kill(pid, SIGUSR1); wait(NULL ); } else { perror("fork failed" ); exit (1 ); } return 0 ; }
代码执行结果:
代码执行结果
参考资料:
Linux进程间通信讲座3
信号 signal kill
任务6:在Windows或Linux中模拟哲学家就餐,提供死锁和非死锁解决方案
任务要求:
提供可能导致死锁的解决方案和绝对不会导致死锁的解决方案。
对于可能导致死锁的解决方案,参考课程材料。Windows尝试使用临界区对象(EnterCriticalSection,LeaveCriticalSection);Linux尝试使用互斥锁(pthread_mutex_lock,pthread_mutex_unlock)。
绝对不会导致死锁的解决方案,例如:尝试同时拿起两根筷子,如果都能拿起则拿起,否则不拿起任何一根。
Linux尝试互斥函数pthread_mutex_lock,pthread_mutex_trylock等。
为增强随机性,保持状态之间的随机持续时间为100ms-500ms。
[可选] 图形界面显示哲学家拿起筷子、吃饭、放下筷子、思考等。
死锁解决方案
死锁解决方案的问题在于每个哲学家先拿起左边的筷子,然后再拿起右边的筷子。当他们同时拿起左边的筷子时,没有哲学家能拿到右边的筷子,造成死锁问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #define PHILOSOPHER_COUNT 5 pthread_mutex_t chopsticks[PHILOSOPHER_COUNT];int get_random_sleep_time () { return rand() % 400000 + 100000 ; } void *philosopher (void *num) { int id = *(int *)num; while (1 ) { printf ("哲学家 %d 在思考\n" , id); usleep(get_random_sleep_time()); printf ("哲学家 %d 在休息\n" , id); usleep(get_random_sleep_time()); pthread_mutex_lock(&chopsticks[id]); printf ("哲学家 %d 拿起左边的筷子\n" , id); pthread_mutex_lock(&chopsticks[(id + 1 ) % PHILOSOPHER_COUNT]); printf ("哲学家 %d 拿起右边的筷子\n" , id); printf ("哲学家 %d 在吃饭\n" , id); usleep(get_random_sleep_time()); pthread_mutex_unlock(&chopsticks[(id + 1 ) % PHILOSOPHER_COUNT]); pthread_mutex_unlock(&chopsticks[id]); } return NULL ; } int main () { pthread_t philosophers[PHILOSOPHER_COUNT]; int philosopher_numbers[PHILOSOPHER_COUNT]; for (int i = 0 ; i < PHILOSOPHER_COUNT; ++i) { pthread_mutex_init(&chopsticks[i], NULL ); philosopher_numbers[i] = i; } for (int i = 0 ; i < PHILOSOPHER_COUNT; ++i) { pthread_create(&philosophers[i], NULL , philosopher, &philosopher_numbers[i]); } for (int i = 0 ; i < PHILOSOPHER_COUNT; ++i) { pthread_join(philosophers[i], NULL ); } return 0 ; }
死锁状态:
死锁状态
可以观察到,他们都同时拿起左边的筷子,造成死锁现象。
非死锁解决方案
我对死锁的解决方案是哲学家先尝试拿起左边的筷子,然后再拿起右边的筷子。与上述不同的是,如果哲学家无法拿到右边的筷子吃饭,他们会放下当前拿着的左边的筷子,从而避免死锁问题。
在具体函数上:pthread_mutex_trylock函数替代pthread_mutex_lock以避免阻塞。如果无法立即获取锁,则不会进入等待状态,从而避免死锁情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 #include <pthread.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> #define PHILOSOPHER_COUNT 5 pthread_mutex_t chopsticks[PHILOSOPHER_COUNT];int get_random_sleep_time () { return rand() % 400000 + 100000 ; } void *philosopher (void *num) { int id = *(int *)num; int left = id; int right = (id + 1 ) % PHILOSOPHER_COUNT; while (1 ) { printf ("哲学家 %d 在思考\n" , id); usleep(get_random_sleep_time()); printf ("哲学家 %d 在休息\n" , id); usleep(get_random_sleep_time()); if (pthread_mutex_trylock(&chopsticks[id]) == 0 ) { printf ("哲学家 %d 拿起左边的筷子\n" , id); sleep(1 ); if (pthread_mutex_trylock(&chopsticks[(id + 1 ) % PHILOSOPHER_COUNT]) == 0 ) { printf ("哲学家 %d 拿起右边的筷子\n" , id); printf ("哲学家 %d 在吃饭\n" , id); usleep(get_random_sleep_time()); pthread_mutex_unlock(&chopsticks[(id + 1 ) % PHILOSOPHER_COUNT]); printf ("哲学家 %d 放下右边的筷子\n" , id); } pthread_mutex_unlock(&chopsticks[id]); printf ("哲学家 %d 放下左边的筷子\n" , id); } } return NULL ; } int main () { pthread_t philosophers[PHILOSOPHER_COUNT]; int philosopher_numbers[PHILOSOPHER_COUNT]; for (int i = 0 ; i < PHILOSOPHER_COUNT; ++i) { pthread_mutex_init(&chopsticks[i], NULL ); philosopher_numbers[i] = i; } for (int i = 0 ; i < PHILOSOPHER_COUNT; ++i) { pthread_create(&philosophers[i], NULL , philosopher, &philosopher_numbers[i]); } for (int i = 0 ; i < PHILOSOPHER_COUNT; ++i) { pthread_join(philosophers[i], NULL ); } return 0 ; }
非死锁程序执行结果:
非死锁程序执行结果
这里我故意让哲学家在拿起左边的筷子后睡得更久,造成他们都同时拿起左边的筷子冲突。从图中可以看到,所有5个哲学家确实同时拿起了左边的筷子。与死锁方法不同,哲学家4主动放下了左边的筷子,重新进入思考状态,避免了死锁情况。
参考资料:
哲学家就餐问题
五个哲学家就餐问题
任务7:学习Linux内核并使用printk调试进程创建和调度策略相关信息
要求:编写应用程序Hello.c,调用fork创建进程,跟踪新创建的子进程在内核中的fork过程,并显示与调度策略相关的PCB成员变量。
编写应用程序Hello.c,调用fork创建子进程(功能无限),打印父子进程的ID号。
在内核中适当位置使用printk(如do_fork函数中的某个地方)输出调试信息,如“当前创建进程对应cmd、进程ID和父进程ID”。
为避免do_fork函数频繁输出上述调试信息,必须限制仅在Hello程序中调用fork时输出上述调试信息。请思考如何实现。
参考方法:内核设计全局变量bool
flag和系统调用SetDebug(bool),SetDebug可以修改flag值为true或false。在Hello程序中,在调用fork函数前调用SetDebug(true),在调用fork函数后调用SetDebug(false)以修改flag。当printk调试信息时,检查flag以确定是否使用printk输出调试信息。
友情提醒:do_fork函数在新版本的Linux内核源代码中已被kernel_clone函数替代。
修改./kernel/fork.c文件
在kernel_clone中使用printk打印相关信息。p是指向task_struct结构的指针,comm是当前执行的命令,pid是子进程ID,current->pid是父进程ID,current是指向父进程的宏。
在kernel_clone中添加printk代码
添加新的系统调用setdebug用于设置debug_fork_flag。bool值debug_fork_flag是一个全局变量,用于控制是否输出。
setdebug系统调用
添加声明
系统调用:./kernel/fork.c(已修改)
系统调用函数声明:./include/linux/syscalls.h
ID:./arch/x86/entry/syscalls/syscall_64.tbl
ID声明:./include/uapi/asm-generic/unistd.h
其余3个文件的修改操作已在实验1的添加系统调用任务中实践过,因此在此不再重复。
编译内核
1 2 3 4 5 6 make mrproper make clean make -j6 make modules_install make install
编写测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <stdio.h> #include <unistd.h> #include <sys/syscall.h> #define SYS_SETDEBUG 447 void SetDebug (int value) { syscall(SYS_SETDEBUG, value); } int main () { SetDebug(1 ); pid_t pid = fork(); if (pid == 0 ) { printf ("子进程ID:%d\n" , getpid()); } else if (pid > 0 ) { printf ("父进程ID:%d\n" , getpid()); } else { perror("fork" ); return 1 ; } SetDebug(0 ); return 0 ; }
为了测试功能,我在另一个测试代码中注释掉SetDebug函数,并同时执行两个程序。预期效果是,只有调用SetDebug函数的程序在fork期间会输出调试信息。
test1未启用打印信息,test2启用打印信息。
运行test1和test2
使用dmesg查看信息。在下图中,我们可以看到背景信息中有一行关于创建进程的信息,cmd=test2,并且与上图中的子进程和父进程PID进行比较,它们也是一致的。
dmesg打印背景信息
参考资料:
Linux内核进程 -
do_fork()函数实现原理
Linux内核源代码fork解释