以前的笔记里写过linux信号相关的使用,当时是读apue TLPI等书籍时记录的关于信号的一些编程知识。最近又改了一些shell脚本的bug,本篇笔记权当个人总结,主要从修改过的典型bug串起各知识点,文章延续之前想到哪写到哪的风格,如果内容有误,欢迎留言指出,不胜感激。
进程状态
D 不可中断 uninterruptible sleep (usually IO)
R 运行 runnable (on run queue)
S 中断 sleeping
T(t) 停止 traced or stopped
Z 僵死 a defunct ("zombie") process
重点介绍下状态为T的进程,可以使用如下例子
ping www.baidu.com > output.txt 2>&1 &
然后再开一个窗口,tail -f output.txt
然后通过kill -STOP/-CONT 进程号,观察tail的输出,验证进程暂停和恢复。
实际演示例子:
[root c++]#ping www.baidu.com > output.txt 2>&1 &
[1] 22566
我们tailf output.txt
在新开一个窗口使用如下命令暂停和恢复进程
kill -STOP 22566
kill -CONT 22566
同样也可以使用如下数字形式:
[root ~]#kill -19 22566
[root ~]#kill -18 22566
这里所有的信号都可以通过命令kill -l获得:
[root singal]#kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
具体信号的含义参考Unix信号
使用如下命令查看进程状态:
ps -aux
root 22566 0.0 0.1 36132 2972 pts/0 S 00:22 0:00 ping www.baidu.com 运行状态
root 22566 0.0 0.1 36132 2972 pts/0 T 00:22 0:00 ping www.baidu.com 暂停状态
进程状态为T的另一种形式:traced,这里我所知道的一种情况是使用gdb挂进来的情况。
例如如下代码:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
while(1)
{
int a = 10;
int b = 20;
int c = a + b;
sleep(1);
}
return 9;
}
编译运行:
gcc -g -o trace trace.c
./trace
在另一个终端里执行如下命令:
[root c++]#ps -ef | grep trace
root 19835 22529 0 23:44 pts/2 00:00:00 ./trace
root 19881 2756 0 23:45 pts/0 00:00:00 grep --color=auto trace
[root c++]#
[root c++]#gdb -p 19835
我们再开一个终端查看trace进程状态:
[root c++]#ps -aux | grep trace | grep -v grep
root 19835 0.0 0.0 4376 788 pts/2 t+ 23:44 0:00 ./trace
这里也可以验证一点,如果你再开一个窗口想挂进这个进程里看发生了什么,会有如下报错提醒:
[root c++]#ps -ef | grep trace
root 19835 22529 0 23:44 pts/2 00:00:00 ./trace
root 21887 22399 0 23:56 pts/1 00:00:00 grep --color=auto trace
[root c++]#gdb -p 19835
GNU gdb (Ubuntu 8.1-0ubuntu3.2) 8.1.0.20180409-git
Copyright (C) 2018 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-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".
Attaching to process 19835
Could not attach to process. If your uid matches the uid of the target
process, check the setting of /proc/sys/kernel/yama/ptrace_scope, or try
again as the root user. For more details, see /etc/sysctl.d/10-ptrace.conf
warning: process 19835 is already traced by process 20286
ptrace: Operation not permitted.
(gdb)
这里有个关键的告警信息:
warning: process 19835 is already traced by process 20286
讲到这里忽然想到之前有同事做了一件非常有趣的事情,为了检测到程序崩溃,直接调试现场环境,修改代码,使得如果崩溃直接挂到gdb,结果由于不知道在哪台机器连接的linux,导致又不能重复挂进去,这个时候除了看日志,没有别的有效的手段,pstack也不能显示程序在执行什么,最终的解决方法只能30 40台跳板机中一个一个去查看。
[root c++]#pstack 19835
Could not attach to target 19835: Operation not permitted.
detach: No such process
查看进程状态
ps -aux查看所有进程状态
ps -axjf显示进程树
ps -aux | egrep '(cron|syslog)'显示与cron和syslog相关的信息
关于PID PPID PGID SID 有几篇非常不错的文章参考:PID, PPID, PGID与SID
[root c++]#ps -axjf | grep sshd
1 1530 1530 1530 ? -1 Ss 0 0:02 /usr/sbin/sshd -D
1530 2294 2294 2294 ? -1 Ss 0 0:02 \_ sshd: root@pts/0
1530 22293 22293 22293 ? -1 Ss 0 0:00 \_ sshd: root@pts/1
22399 2865 2864 22399 pts/1 2864 S+ 0 0:00 | \_ grep --color=auto sshd
1530 22454 22454 22454 ? -1 Ss 0 0:00 \_ sshd: root@pts/2
[root c++]#
[root c++]#
[root c++]#ps -ef | grep sshd
root 1530 1 0 Mar16 ? 00:00:02 /usr/sbin/sshd -D
root 2294 1530 0 Mar16 ? 00:00:02 sshd: root@pts/0
root 2885 22399 0 11:07 pts/1 00:00:00 grep --color=auto sshd
root 22293 1530 0 Mar20 ? 00:00:00 sshd: root@pts/1
root 22454 1530 0 Mar20 ? 00:00:00 sshd: root@pts/2
[root c++]#
[root c++]#ps -aux | grep sshd
root 1530 0.0 0.2 72300 5584 ? Ss Mar16 0:02 /usr/sbin/sshd -D
root 2294 0.0 0.3 110080 7028 ? Ss Mar16 0:02 sshd: root@pts/0
root 2917 0.0 0.0 21536 1116 pts/1 S+ 11:07 0:00 grep --color=auto sshd
root 22293 0.0 0.3 110080 7176 ? Ss Mar20 0:00 sshd: root@pts/1
root 22454 0.0 0.3 110080 7076 ? Ss Mar20 0:00 sshd: root@pts/2
[root c++]#ps -aux | egrep '(cron|syslog)'
root 656 0.0 0.1 38428 3020 ? Ss Mar16 0:01 /usr/sbin/cron -f
message+ 664 0.3 0.2 50840 4988 ? Ss Mar16 27:01 /usr/bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation --syslog-only
syslog 748 0.0 0.2 263040 4272 ? Ssl Mar16 0:03 /usr/sbin/rsyslogd -n
root 3965 0.0 0.0 21536 1152 pts/1 S+ 11:13 0:00 grep -E --color=auto (cron|syslog)
此外我个人较少使用的方式是:
指定要显示的字段
ps -eo user,stat,cmd
这里除了user stat还有如下可以指定的字段
user 用户名
uid 用户号
pid 进程号
ppid 父进程号
size 内存大小, Kbytes字节.
vsize 总虚拟内存大小, bytes字节(包含code+data+stack)
share 总共享页数
nice 进程优先级(缺省为0, 最大为-20)
priority(pri) 内核调度优先级
pmem 进程分享的物理内存数的百分比
trs 程序执行代码驻留大小
rss 进程使用的总物理内存数, Kbytes字节
time 进程执行起到现在总的CPU暂用时间
stat 进程状态
cmd(args) 执行命令的简单格式
查看当前系统进程的uid,pid,stat,pri, 以uid号排序.
[root c++]#ps -eo pid,stat,pri,uid --sort uid
PID STAT PRI UID
1 Ss 19 0
2 S 19 0
4 I< 39 0
6 I< 39 0
7 S 19 0
8 I 19 0
9 I 19 0
10 S 139 0
11 S 139 0
12 S 19 0
13 S 19 0
14 S 139 0
15 S 139 0
16 S 19 0
18 I< 39 0
19 S 19 0
20 I< 39 0
21 S 19 0
22 S 19 0
25 S 19 0
26 S 19 0
27 I< 39 0
28 S 19 0
29 SN 14 0
30 SN 0 0
31 I< 39 0
查看当前系统进程的user,pid,stat,rss,args, 以rss排序.
[root c++]#ps -eo user,pid,stat,rss,args --sort rss
USER PID STAT RSS COMMAND
root 2 S 0 [kthreadd]
root 4 I< 0 [kworker/0:0H]
root 6 I< 0 [mm_percpu_wq]
root 7 S 0 [ksoftirqd/0]
root 8 I 0 [rcu_sched]
root 9 I 0 [rcu_bh]
root 10 S 0 [migration/0]
root 11 S 0 [watchdog/0]
root 12 S 0 [cpuhp/0]
linux下还有一种查看进程信息的方法,就是通过虚拟文件系统/proc,top命令等应该也是从/proc中获取的数据。例如我们验证通过ps看到的进程状态和从/proc中看到的状态是否一致:
[root c++]#ps -aux | grep trace
root 9406 0.0 0.0 21536 1100 pts/1 S+ 11:45 0:00 grep --color=auto trace
root 19835 0.0 0.0 4376 788 pts/2 t+ Mar20 0:00 ./trace
[root c++]#
[root c++]#cat /proc/19835/status
Name: trace
Umask: 0022
State: t (tracing stop)
Tgid: 19835
Ngid: 0
Pid: 19835
PPid: 22529
TracerPid: 20286
Uid: 0 0 0 0
Gid: 0 0 0 0
在编程中也经常使用/proc/pid/cmdline,下面是一个简单的例子:
/* 获取进程命令行参数 */
void get_cmd_by_pid(pid_t pid, char *cmd)
{
char buf[TMP_BUF_SIZE];
int i = 0;
snprintf(buf, TMP_BUF_SIZE, "/proc/%d/cmdline", pid);
FILE* fp = fopen(buf, "r");
if(fp == NULL)
{
return;
}
//敏感信息使用完内存中清空
memset(buf, 0, TMP_BUF_SIZE);
size_t ret = fread(cmd, 1, TMP_BUF_SIZE - 1, fp);
/*
*需要下面for循环的原因是
*man手册资料
*This holds the complete command line for the process, unless the process is a zombie.
*In the latter case,there is nothing in this file: that is, a read on this file will return 0
*characters. The command-line arguments appear in this file as a set of strings separated by
*null bytes ('\0'), with a further null byte after the last string.
*/
for (i = 0; ret != 0 && i < ret - 1; i++)
{
if (cmd[i] == '\0')
{
cmd[i] = ' ';
}
}
fclose(fp);
cmd[TMP_BUF_SIZE - 1] = '\0';
}
程序后台运行
nohup是一个POSIX命令,用于忽略SIGHUP(挂断信号),SIGHUP是终端注销时所发送至程序的一个信号。nohup在默认情况下(没有使用重定向时)会输出一个名叫nohup.out的文件到终端上。
下例中,nohup用于忽略SIGHUP信号,&使命令于后台执行,因此终端退出后命令仍旧执行。
nohup 命令名 &
值得注意,这种方法防止命令在注销时被忽略SIGHUP信号,但,如果该命令对标准I/O文件(stdin,stdout,或stderr)进行输入/输出,那么该命令仍旧可能被终端挂起。[1] 详情请看下文的 阻止挂起 。 另外,nohup 命令常常和nice命令一起执行,以调整命令/程序的优先级。
nohup nice 命令 &
验证例子如下:
#正如上面讲的,如果nohup命令没有处理标准输入 输出 错误,可能会导致终端挂起时命令挂起。
[root ~]#nohup ping www.baidu.com </dev/null >nohup.out 2>nohup.err &
[1] 30378
[root ~]#
iTerm2中打开新的tab标签,查看进程信息如下:
[root ~]#ps -ef | grep 30378
root 30378 28012 0 16:12 pts/1 00:00:00 ping www.baidu.com
root 30689 28012 0 16:14 pts/1 00:00:00 grep --color=auto 30378
我们关闭第一个终端,再继续查看ping进程依旧存在
[root ~]#ps -ef | grep 30378
root 30378 28012 0 16:12 pts/1 00:00:00 ping www.baidu.com
root 30689 28012 0 16:14 pts/1 00:00:00 grep --color=auto 30378
详细解释nohup: ignoring input - what does it mean?
io重定向
上面nohup中的例子中有重定向标准输入输出错误,这里不展开讲,只讲shell中另一种常见的形式。
n<&-
Close input file descriptor n.
0<&-, <&-
Close stdin.
n>&-
Close output file descriptor n.
1>&-, >&-
Close stdout.
由于子进程继承父进程打开的文件描述符,因此在脚本中经常使用上面的方法关闭文件描述符。
如果我们使用cat显示文件时把标准输出关闭会报错:
[root ~]#cat /etc/passwd >&-
cat: standard output: Bad file descriptor
[root ~]#
使用lsof可以查看文件描述符打开情况,一篇非常好的介绍文章lsof command
一般的套路是与flock一起使用:
parent.sh
#!/bin/bash
set -e
#实现单例
LOCK_FILE=${0}.lock
[ -f $LOCK_FILE ] || touch $LOCK_FILE
exec 9<> $LOCK_FILE
flock -no 9 || exit 0
trap "rm -f $LOCK_FILE" EXIT
SERVICE_PATH=/root/shell/child.sh
$SERVICE_PATH 9>&- &
#查找打开文件的pid
output=`lsof -t $LOCK_FILE`
pid=$$
echo $pid > parent
echo $output >> parent
child.sh
#!/bin/bash
output=`lsof -t parent.sh.lock`
pid=$$
echo $pid > child
echo $output >> child
运行./parent.sh之后,可以根据parent和child中的内容判定,child.sh中没有继承打开的文件描述符。
这里用到了一个有争议的命令set -e,作用是set -e之后出现的代码,一旦执行返回值不等于0脚本立刻退出。这里之前改过一个bug,在使用了set -e的脚本中新增了一个函数,这个函数可能返回0 1 2三个值,于是问题出现了,这个调用逻辑不是主流程必现调用,因此这个问题过了几个月才发现。