df 和 ls 命令执行夯主

作者:张首富
时间:2020-05-21
w x:y18163201

原因

今天一个朋友问我一个问题,他的描述如下:

1,ls / 执行这个命令的时候卡住,什么反应都没有,但是 ls 其他目录的时候就没有事情

2,df 查看文件挂载的时候也卡住,

3,我什么都没干啊,就做了一个 ISCSI

其实他说第二点问题的时候我就已经猜到问题所在了,那不就是远程挂载的磁盘非正常的掉了,然后就会造成这个问题。但是他说 ISCSI 这个玩意的时候我不知道是啥,于是查了一下,有兴趣的同学可以看看这是:https://zhuanlan.zhihu.com/p/60986068,看的出来他是一个网络存储,那么就更加坚定我的想法了,开始指挥解决问题。

解决办法

问题原因大概猜想出来之后,就是开始解决问题了。

找到是哪个挂载点失效了

我们可以使用此命令来查找挂载点哪个地方失效了。

strace df -Th

执行卸载

通过上面命令我们能找到是哪个挂载点失效了,我们可以使用下面命令先尝试卸载

umount -lf 有问题的挂载点

然后等个1~2 分钟看看效果,-f 是强制卸载的意思。

如果使用这个命令还是解决不了问题,那么我们先确认有哪些进程在占用这个挂载点

fuser -cu 挂载点

会得到进程 ID,和进程命令,看看此进程是否能正常杀掉,如果可以我们手工停止这个进程,然后进行卸载,或者直接使用下面命令进行卸载

fuser -ck 挂载点

strace 工具实现原理

什么是 strace

它是一个具有 Linux 传统命令行界面的诊断、调试和指令性用户空间的使用程序。它用于监控和篡改进程与 Linux 内核之间的交互,包括系统调用、信号传递和进程状态的变化。strace 的所有操作都是通过内核的ptrace功能来实现的。项目主页是:https://github.com/strace/strace;

关于 ptrace 相关知识可以阅读:

main ptrace

Linux 沙箱之 ptrace

https://jin-yang.github.io/post/linux-ptrace-api-introduce.html

使用 strace

最简单的 strace 命令的用法就是: strace PROG;PROG 就是要执行的程序(linux 命令)。strace 命令执行的结果就是按照调用顺序打印出所有的系统调用,包括函数名、参数列表以及返回值。

使用 strace 跟踪一个进程的系统调用的基本流程如下图:

df 和 ls 命令执行夯主_第1张图片

(此图片来源网络)

从图中可以看出strace做了以下几件事情:

  1. 设置SIGCHLD 信号的处理函数,这个处理函数只要不是SIG_IGN即可。由于子进程停止后是通过SIGCHLD信号通知父进程的,所以这里要防止SIGCHLD信号被忽略。

  2. 创建子进程,在子进程中调用ptrace(PTRACE_TRACEME,0L, 0L, 0L)使其被父进程跟踪,并通过execv函数执行被跟踪的程序。
  3. 通过wait()等待子进程停止,并获得子进程停止时的状态status。
  4. 通过子进程的状态查看子进程是否已正常退出,如果是,则不再跟踪,随后调用ptrace发送PTRACE_DETACH请求解除跟踪关系。
  5. 子进程停止后,打印系统调用的函数名、参数和返回值。具体流程见图2。
  6. 通过PTRACE_SYSCALL让子进程继续运行,由于这个请求会让子进程在系统调用的入口处和系统调用完成时都会停止并通知父进程,这样,父进程就可以在系统调用开始之前获得参数,结束之后获得返回值。

在系统调用的入口和结束时子进程停止运行时,这时父进程认为子进程是因为收到SIGTRAP信号而停止的。所以父进程在wait()后可以通过SIGTRAP来与其他信号区分开。

Strace中为每个要跟踪的进程维护了一个TCB(Trace Control Block)结构,定义如下。它保存了当前发生的系统调用的信息。

/* Trace Control Block */
struct tcb {
    int flags;    /* See below for TCB_ values */
    int pid;      /* Process Id of this entry */
    int qual_flg; /* qual_flags[scno] or DEFAULT_QUAL_FLAGS + RAW*/
    int u_error;  /* Error code */
    long scno;    /* System call number */
    long u_arg[MAX_ARGS];    /* System call arguments */
    long u_rval;      /* Return value */
    int curcol;       /* Output column for this process */
    FILE *outf;       /* Output file for this process */
    const char *auxstr;/*Auxiliary info from syscall (see RVAL_STR) */
    const struct_sysent *s_ent;/* sysent[scno] or dummy struct for bad scno */
    struct timeval stime;/*System time usage as of last process wait */
    struct timeval dtime;    /* Delta for system time usage */
    struct timeval etime;    /* Syscall entry time */
              /* Support fortracing forked processes: */
    long inst[2];     /* Saved clone args (badly named) */
};

上面已经提到,子进程会在系统调用前后各停止一次,所以打印系统调用信息时分为两个阶段:在系统调用开始时可以获取系统调用号和参数,在系统调用结束时可以获取系统调用的返回结果。通过给tcb结构的flags字段清除和添加TCB_INSYSCALL标志位来区分系统调用的开始和结束。

df 和 ls 命令执行夯主_第2张图片

例如编写一个使用printf打印“Hello world”的程序hello.c,使用strace跟踪该程序的系统调用可以看到如下结果:

# ./strace ./hello
execve("./hello ", ["./hello "], [/* 7 vars */])= 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS,-1, 0) = 0x2aaad000
stat("/etc/ld.so.cache", 0x7faf4ca8)    = -1 ENOENT (No such file or directory)
open("/tmp/libgcc_s.so.1", O_RDONLY)    = -1 ENOENT (No such file or directory)
open("/lib/libgcc_s.so.1", O_RDONLY)    = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=1565445, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS,-1, 0) = 0x2aaae000
read(3,"\177ELF\1\2\1\0\0\0\0\0\0\0\0\0\0\3\0\10\0\0\0\1\0\0\263\200\0\0\0004"...,4096) = 4096
mmap(NULL, 241664, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =0x2aabe000
mmap(0x2aabe000, 169308, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED,3, 0) = 0x2aabe000
mmap(0x2aaf8000, 2400, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED,3, 0x2a000) = 0x2aaf8000
close(3)                                = 0
munmap(0x2aaae000, 4096)                = 0
open("/tmp/libc.so.0", O_RDONLY)        = -1 ENOENT (No such file or directory)
open("/lib/libc.so.0", O_RDONLY)        = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=431732, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS,-1, 0) = 0x2aaae000
read(3, "\177ELF\1\2\1\0\0\0\0\0\0\0\0\0\0\3\0\10\0\0\0\1\0\0\252\200\0\0\0004"...,4096) = 4096
mmap(NULL, 471040, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =0x2aaf9000
mmap(0x2aaf9000, 380336, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED,3, 0) = 0x2aaf9000
mmap(0x2ab65000, 8088, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED,3, 0x5c000) = 0x2ab65000
mmap(0x2ab67000, 19376, PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x2ab67000
close(3)                                = 0
munmap(0x2aaae000, 4096)                = 0
open("/tmp/libc.so.0", O_RDONLY)        = -1 ENOENT (No such file or directory)
open("/lib/libc.so.0", O_RDONLY)        = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=431732, ...}) = 0
close(3)                                = 0
stat("/lib/ld-uClibc.so.0", {st_mode=S_IFREG|0755,st_size=22604, ...}) = 0
mprotect(0x2ab65000, 4096, PROT_READ)   = 0
mprotect(0x2aabc000, 4096, PROT_READ)   = 0
ioctl(0, TIOCNXCL, {B115200 opost isig icanon echo ...}) = 0
ioctl(1, TIOCNXCL, {B115200 opost isig icanon echo ...}) = 0
write(1, "Hello world\n", 12Hello world
)           = 12
exit(0)                                 = ?
+++ exited with 0 +++

从结果可以看出,执行该程序调用了很多系统调用,并最终通过write系统调用打印出“Hello world”。

跟踪一个正在运行的进程,使用-p选项加上进程的pid。

跟踪某个特定的系统调用,使用-e选项加上系统调用名。

例如,跟踪进程727的epoll_wait系统调用:strace -e epoll_wait -p 727

参考:

https://blog.csdn.net/jasonchen_gbd/article/details/44044539

https://blog.csdn.net/shandianling/article/details/17033299

http://godorz.info/2011/02/process-tracing-using-ptrace/(值得一看)