操作系统实验二
实验目的
理解进程/线程的概念和应用编程过程;
理解进程/线程的同步机制和应用编程;
掌握和推广国产操作系统(推银河麒麟或优麒麟,建议)
实验内容
在Linux/Windows下创建2个线程A和B,循环输出数据或字符串。
在Liunx下创建(fork)一个子进程,实验wait/exit函数
在Windows/Linux下,利用线程实现并发画圆画方。
在Windows或Linux下利用线程实现“生产者-消费者”同步控制
在Linux下利用信号机制(signal)实现进程通信
在Windows或Linux下模拟哲学家就餐,提供死锁和非死锁解法。
研读Linux内核并用printk调试进程创建和调度策略的相关信息。
任务一:在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
编写code并编译
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函数详解:
使用命令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命令展示其详细信息
图中SPID表示的就是线程的ID号,3852和3852就是我们创建的A、B线程
程序运行结果
参考资料:
Linux——线程的创建
ps命令介绍
ps命令手册大全
任务二:在Liunx下创建(fork)一个子进程,实验wait/exit函数
要求:
效果1:父进程不用wait函数,让父进程先于子进程结束,子进程进入死循环或较长时间的循环,观察父子进程的进程ID和父进程ID。
程序中printf各进程的进程号和父进程号。注意,父进程和子进程的输出请给出相应的提示字符串以便相互区分,后同
同时,用ps命令显示进程列表,观察指定进程的进程ID和父进程ID,和printf输出的这些ID是否一致,并解释。
效果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,然后父进程经历20s结束后,子进程打印自己的PID和父进程的PID,在父进程未结束的20s内,子进程打印的父进程PID=3604,但父进程结束后,子进程继续运行,此时父进程的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()详解
任务三:在Windows/Linux下,利用线程实现并发画圆画方
本任务我采用了Qt6实现了双线程画圆画方的GUI界面。在此只解释核心代码的部分:
由于Qt的绘制动作只能够在主线程中完成 ,所以我们画圆和方的两个线程分别用于计算圆和方的坐标点,并传给主线程,主线程完成相应的绘制动作。
项目文件目录如下:
首先Qt想要绘制图形需要在MainWindow类中overwrite一下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信号(signal),把该坐标传入主线程,主线程进行处理。
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)) ; }
对于圆线程类,需要overwrite一下run函数,run函数在线程start的时候被调用,具体的逻辑就是隔一段时间进行process函数的调用,process函数处理点的信息然后发送给主线程。
1 2 3 4 5 6 7 void CircleThread::run () { while (!isInterruptionRequested ()) { process (); if (circleAngle >= 2 * M_PI) break ; msleep (62 ); } }
主线程中需要做的是初始化一个圆线程类的实例,并connect前面提到的circlePoint信号和处理点的Slot槽。需要注意的是Slot函数是个Lambda表达式的写法,把子线程传回来的点加入圆点集中,然后update绘制。
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 (); });
最后一步的处理比较的简单,我们需要启动线程,线程的启动由button“开始绘制”控制,点击则启动两个线程。
1 2 3 4 5 6 7 void MainWindow::on_startpaint_clicked () { circleThread->start (); squareThread->start (); }
最终启动程序,程序运行效果图如下:
参考资料:
Qt中多线程写法一(步骤讲解+代码+加演示)
Qt Documentation
任务四:在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 ("Producer %d produced %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 ("Consumer %d consumed %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 ("Producer %d produced %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 ("Consumer %d consumed %d\n" , id, product);pthread_mutex_unlock(&mutex); sem_post(&empty);
程序运行结果如下:
利用ps命令查看当前进程中的所有线程:
参考资料:
线程的同步问题–生产者 消费者
任务五:在Linux下利用信号机制(signal)实现进程通信
任务要求:
父进程创建(fork)子进程,并让子进程进入死循环。
子进程每隔2秒输出“I am Child Process, alive !\n”
父进程询问用户“To terminate Child Process. Yes or No? \n”要求用户从键盘回答Y或N.若用户回答N,延迟2秒后再提问.
若用户回答Y,向子进程发送用户信号,让子进程结束。
子进程结束之前打印字符串:”Bye,Wolrd !\n”
函数: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进程间通信第三讲 信号signal kill
任务六:在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 ; }
非死锁程序运行结果:
这里我有意通过哲学家拿到左手的筷子后sleep一个较长的时间,让他们产生同时拿到左手筷子的冲突,通过图可以看到,5个哲学家的确同时拿到了左手边的筷子,与死锁写法不同的是,途中哲学家4主动放下了左边的筷子,重新进入思考状态,避免了死锁的状态。
参考资料:
哲学家问题
五个哲学家就餐问题
任务七:研读Linux内核并用printk调试进程创建和调度策略的相关信息
要求:编写应用程序Hello.c,调用fork创建进程,在内核中跟踪该新建子进程的fork过程和显示与调度策略相关的PCB成员变量。
编写应用程序Hello.c,在其中调用fork创建子进程(功能不限),打印出父子进程的ID号。
在内核中合适的位置(譬如do_fork函数内的某处)用printk输出“当前正创建的进程对应cmd,进程ID和父进程ID”等调试信息。
为避免do_fork函数频繁地输出上述调试信息,须限定仅在Hello程序中调用fork时才输出上述调试信息,请思考要如何实现。
参考方法:内核设计全局变量bool flag和系统调用SetDebug(bool),SetDebug可以修改flag的值为true或false。在Hello程序中的调用fork函数前后,分别调用SetDebug(true)和SetDebug(false)修改flag。printk调试信息时检查flag以确定是否要使用printk输出调试信息。
温馨提示:do_fork函数再较新版本的linux内核源码中已被kernel_clone函数代替。
修改./kernel/fork.c文件
在kernel_clone中使用printk,打印相关信息,p是指向task_struct结构体的指针,comm即当前执行的command,pid是子进程的进程号,current->pid是父进程的进程号,current是一个宏,指向父进程。
增加新的系统调用setdebug,用于设置debug_fork_flag,bool值debug_fork_flag是用于控制是否输出的全局变量。
增加声明
系统调用:./kernel/fork.c(已经修改)
系统调用函数声明 ./include/linux/syscalls.h
ID:./arch/x86/entry/syscalls/syscall_64.tbl
ID 声明:./include/uapi/asm-generic/unistd.h
剩下3个文件的修改操作在实验一增加系统调用的任务中已经实践过,就不在此赘述。
编译内核
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启用了打印信息。
使用dmesg查看信息,下图可以看到后台信息有一行正在创建的进程,cmd=test2,然后对比上图的子进程和父进程的PID,发现也是一致的。
参考资料:
Linux内核进程——do_fork()函数的实现原理
linux 内核源码 fork 解读