现象背后的本质之使用GDB多线程多进程调式

2017-12-26 10:25:14来源:http://blog.csdn.net/bushipeien/article/details/78889428作者:CSDN博客人点击

分享


NOTE : 上一篇理论请戳这里戳我


一:待解决问题
[[email protected] dhuang]# ./12_1
thread started...
parent about to fork...
preparing locks...
parent unlocking locks...
parent returned from fork
child unlocking locks...
child returned from fork
[[email protected] dhuang]# ./12_1 >temp.txt
[[email protected] dhuang]# cat temp.txt
thread started...
parent about to fork...
preparing locks...
parent unlocking locks...
parent returned from fork
thread started...
parent about to fork...
preparing locks...
child unlocking locks...
child returned from fork
二:分析问题


可以看到输出到文件的话,有些信息打印了两次。笔者一下子就想到了以前笔者深度总结Standard IO的结论了:
这是STANDARD IO
中lined buffer和full buffer的锅,当然帮凶是fork对父进程资源继承的机制。其实问题到这里就解决了,但是这是笔者从原理上分析的结论,必然需要用实验去证实。



为此笔者对做一下修改:1. 将标准输出指向文件,方便GDB调式查看结果。 2. 简化代码逻辑,能够说明本文目的即可。 3. 本文的重点将是如何分析问题并解决问题,而非问题本身。


三:解决问题


3.1 修改代码看看结果


[[email protected] dhuang]# vim 12_1.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <err.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
void prepare(void)
{
printf("preparing locks.../n");
}
void * thr_fn(void *arg)
{
printf("thread started.../n");
fflush(1);//这里专门针对DeBUG
pause();
return(0);
}
int main(void)
{
int err;
pid_t pid;
pthread_t tid;
int fd;
char * buffer="hi, it's a flag to control flow/n";
//可以使用多种手段重定向标准输入到文件
close(1);
if((fd=open("data.txt",O_RDWR|O_CREAT|O_TRUNC,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)) < 0)
errx(1,"error in creating a file/n");
//这里已经将pthread_atfork本来的目的忽略了!
if ((err = pthread_atfork(prepare, NULL, NULL)) != 0)
errx(1, "can’t install fork handlers");
if ((err = pthread_create(&tid, NULL, thr_fn, 0)) != 0)
errx(1, "can’t create thread");
sleep(2);
//这里将会更直观的看到一些区别
write(STDOUT_FILENO,buffer,strlen(buffer));
printf("parent about to fork..../n");
if ((pid = fork()) < 0)
errx(1,"fork failed");
else if (pid == 0)
printf("child returned from fork/n");
else
printf("parent returned from fork/n");
exit(0);
}

运行结果如下:


[[email protected] dhuang]# gcc -pthread -o 12_1 12_1.c
[[email protected] dhuang]# ./12_1
[[email protected] dhuang]# cat data.txt
thread started...
hi, it`s a flag to control flow
parent about to fork....
preparing locks...
parent returned from fork
parent about to fork....
preparing locks...
child returned from fork


可以看到thread started...
及hi,it's...
,parent about to fork...
输出了两次,所以到这里已经很明确了,笔者通过修改的代码验证了最上面的理论分析。但是还是不够,没有做GDB的跟踪,因为GDB值得好好掌握啊,这是个练手的机会。



3.2 多线程及多进程GDB跟踪



首先重新编译增加调试信息,本程序正好fork
前跟踪另一个线程
,fork
后跟踪另一个进程



3.2.1 跟踪线程:


[[email protected] dhuang]# gcc -pthread -o 12_1 12_1.c -g
[[email protected] dhuang]# gdb
GNU gdb (GDB) Fedora 8.0.1-33.fc27
Copyright (C) 2017 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
(gdb) file 12_1
Reading symbols from 12_1...done.
(gdb) break 14
Breakpoint 1 at 0x4008f4: file 12_1.c, line 14.
(gdb) break main
Breakpoint 2 at 0x400921: file 12_1.c, line 25.
(gdb) r
Starting program: /root/dhuang/12_1
Missing separate debuginfos, use: dnf debuginfo-install glibc-2.26-20.fc27.x86_64
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Breakpoint 2, main () at 12_1.c:25
25char * buffer="hi, it's a flag to control flow/n";
(gdb) set scheduler-locking on
(gdb) c
Continuing.
[New Thread 0x7ffff77d2700 (LWP 8431)]
[Switching to Thread 0x7ffff77d2700 (LWP 8431)]
Thread 2 "12_1" hit Breakpoint 1, thr_fn (arg=0x0) at 12_1.c:14
14printf("thread started.../n");
(gdb) info thread
Id Target Id Frame
1Thread 0x7ffff7fd5740 (LWP 8427) "12_1" 0x00007ffff78aead0 in nanosleep () from /lib64/libc.so.6
* 2Thread 0x7ffff77d2700 (LWP 8431) "12_1" thr_fn (arg=0x0) at 12_1.c:14
(gdb) next
15fflush(stdout);//这里专门针对DeBUG
(gdb)
//-------------------------------------------------------------------
//此时在另一个终端看看data.txt文件的结果,以上fflush语句还未执行
[[email protected] dhuang]# cat data.txt
//什么都没有
(gdb) next
16pause();
(gdb)
//-----------------------------------------------------------------
//此时再次在另一个终端观察结果,发现文件里面有了输出。所以因为fflush的缘故,刷新了标准输出。
[[email protected] dhuang]# cat data.txt
thread started...
[[email protected] dhuang]#
//笔者强调下GDB没有退出过,所以还是接着上面的操作来的
(gdb) thread 1
[Switching to thread 1 (Thread 0x7ffff7fd5740 (LWP 8427))]
#00x00007ffff78aead0 in nanosleep () from /lib64/libc.so.6
(gdb) break 36
Breakpoint 3 at 0x4009dc: file 12_1.c, line 36.
(gdb) c
Continuing.
Thread 1 "12_1" hit Breakpoint 3, main () at 12_1.c:36
36write(STDOUT_FILENO,buffer,strlen(buffer));
(gdb) next
37printf("parent about to fork..../n");
(gdb) next
38if ((pid = fork()) < 0)
(gdb)
//-----------------------------------------------------------------
//在另一个终端看看结果
[[email protected] dhuang]# cat data.txt
thread started...
hi, it's a flag to control flow


以上跟踪调试的结果很好的和笔者的分析吻合,因为头一个输出使用了fflush
刷新缓冲区,另一个输出使用了低层次的I/Owrite
。其实到这里本文已经可以结尾了,但是多进程的部分做个最后的验证吧。



3.2.2 跟踪进程:


//笔者强调下GDB没有退出过,所以还是接着上面的操作来的
(gdb) set follow-fork-mode child
(gdb) set detach-on-fork off
(gdb) break 41
Breakpoint 4 at 0x400a2e: file 12_1.c, line 41.
(gdb) c
Continuing.
[New process 8512]
Reading symbols from /root/dhuang/12_1...done.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
[Switching to Thread 0x7ffff7fd5740 (LWP 8512)]
Thread 2.1 "12_1" hit Breakpoint 4, main () at 12_1.c:41
41printf("child returned from fork/n");
Missing separate debuginfos, use: dnf debuginfo-install glibc-2.26-20.fc27.x86_64
(gdb) info inferiors
NumDescription Executable
1process 8427/root/dhuang/12_1
* 2process 8512/root/dhuang/12_1
(gdb) next
44exit(0);
(gdb) next
[Inferior 2 (process 8512) exited normally]
(gdb)
//-----------------------------------------------------------------
//注意这里让子进程结束了
[[email protected] dhuang]# cat data.txt
thread started...
hi, it`s a flag to control flow
parent about to fork....
preparing locks...
child returned from fork
[[email protected] dhuang]#
//子进程因为exit(0),所以刷新了缓冲区,这里就可以看到子进程的输出了。注意`parent about to fork....`和`preparing locks...`
(gdb) break 43
Breakpoint 5 at 0x400a3a: /root/dhuang/12_1.c:43. (2 locations)
(gdb) c
Continuing.
Thread 1 "12_1" hit Breakpoint 5, main () at 12_1.c:43
43printf("parent returned from fork/n");
(gdb) next
44exit(0);
(gdb) next
[Thread 0x7ffff77d2700 (LWP 8431) exited]
[Inferior 1 (process 8427) exited normally]
(gdb) q
[[email protected] dhuang]#
//-----------------------------------------------------------------
//注意这里让子进程结束了
[[email protected] dhuang]# cat data.txt
thread started...
hi, it`s a flag to control flow
parent about to fork....
preparing locks...
child returned from fork
parent about to fork....
preparing locks...
parent returned from fork
[[email protected] dhuang]#

至此,整个过程完全复现。所以重复下造成题目刚开始输出差别的根本原因是:



标准IO函数会有默认buffer的存在,输出到文件的话是full buffer
,而输出到终端的话是line buffer

fork
子进程会继承大部分父进程的资源,其中就包括打开的文件描述符,还有标准IO 函数所分配的buffer区域!

四:写在最后


本文笔者没有介绍GDB
调试多线程和多进程的用法,本文的重点在于如何分析解决一个问题。而非任意工具的使用,本文程序也并非有实用性,笔者根据问题修改的代码。如pthread_atfork
本来的目的是为了锁在多线程中fork的一致性,这里没有体现这一作用。如果对于笔者以上论述有任何疑问,请及时指正。


NOTE:GDB笔者现学现卖,如果有使用过KEIL调式单片机程序的话,完全就是一样的思路。但是多线程和多进程的调试,首先需要有相应的理论知识。


最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台