BPF(BSD Packet Filter)简介

BPF(BSD Packet Filter)是一种抓取并过滤网络数据包(capture and filter packet)的内核结构(kernel architecture)。BPF包含2个重要的组成部分:网络分流器(network tap)和包过滤器(packet filter)。网络分流器负责从网络驱动拷贝数据包,而包过滤器则过滤掉不符合条件的数据包,只把符合需求的数据包上报给应用程序。下图摘自UNP

BPF

当数据包到达网卡时,正常情况下,数据链路层的驱动程序会把包转给协议栈。但当有BPF监听网卡时,驱动程序会首先把包发给BPFBPF会把包发给不同程序的包过滤器,再由过滤器决定哪些包并且包里的哪些内容应该保存下来。对于符合条件的数据包,BPF会把数据拷贝到和包过滤器对应的缓存(buffer)。然后驱动程序继续执行。

在用tcpdump命令过滤数据包时:

tcpdump -p -ni eth0 -d "ip and udp"

实际就用到了BPF

解决编译错误“fatal error: ‘libelf.h’ file not found”

原文发布于hellogcc网站。

最近在编译一个开源项目时,遇到这个编译错误:

fatal error: 'libelf.h' file not found
#include <libelf.h>
     ^
1 error generated.

解决方法是安装elfutils-libelf-devel这个软件包:

yum install elfutils-libelf-devel

或:

dnf install elfutils-libelf-devel

解决screen会话闪烁的问题

使用GNU screen创建多个工作会话时,使用时可能会遇到会话屏幕闪烁的问题。比如在命令行使用backspace把所有字符都删除完以后,或是man某个命令翻到最后一页还接着往下翻,等等。原因是visual bell在捣鬼。解决方法如下:

(1)编辑“~/.screenrc”文件;
(2)加入以下行:

vbell_msg "bell: window ~%"     # Message for visual bell
vbellwait 2                     # Seconds to pause the screen for visual bell
vbell off                       # Turns visual bell off

参考文档:
http://stackoverflow.com/questions/897358/gnu-screen-refresh-problem

docker笔记(4)—— 如何进入一个正在运行的“docker container”?

docker attach [container-id]”命令有时会hang住,执行Ctrl+C命令也不起作用:

[root@localhost ~]# docker attach a972e69ab444
^C^C^C^C^C^C^C^C^C^C

使用pstack命令查看函数调用栈:

[root@localhost ~]# pstack 29744
Thread 5 (Thread 0x7f9079bd8700 (LWP 29745)):
#0  runtime.futex () at /usr/lib/golang/src/pkg/runtime/sys_linux_amd64.s:269
#1  0x0000000000417717 in runtime.futexsleep () at /usr/lib/golang/src/pkg/runtime/os_linux.c:49
#2  0x0000000001161c58 in runtime.sched ()
#3  0x0000000000000000 in ?? ()
Thread 4 (Thread 0x7f90792d7700 (LWP 29746)):
#0  runtime.futex () at /usr/lib/golang/src/pkg/runtime/sys_linux_amd64.s:269
#1  0x0000000000417782 in runtime.futexsleep () at /usr/lib/golang/src/pkg/runtime/os_linux.c:55
#2  0x00007f907b830f60 in ?? ()
#3  0x0000000000000000 in ?? ()
Thread 3 (Thread 0x7f9078ad6700 (LWP 29747)):
#0  runtime.futex () at /usr/lib/golang/src/pkg/runtime/sys_linux_amd64.s:269
#1  0x0000000000417717 in runtime.futexsleep () at /usr/lib/golang/src/pkg/runtime/os_linux.c:49
#2  0x00000000011618a0 in text/template.zero ()
#3  0x0000000000000000 in ?? ()
Thread 2 (Thread 0x7f9073fff700 (LWP 29748)):
#0  runtime.futex () at /usr/lib/golang/src/pkg/runtime/sys_linux_amd64.s:269
#1  0x0000000000417717 in runtime.futexsleep () at /usr/lib/golang/src/pkg/runtime/os_linux.c:49
#2  0x000000c2080952f0 in ?? ()
#3  0x0000000000000000 in ?? ()
Thread 1 (Thread 0x7f907b9e1800 (LWP 29744)):
#0  runtime.epollwait () at /usr/lib/golang/src/pkg/runtime/sys_linux_amd64.s:385
#1  0x00000000004175dd in runtime.netpoll () at /usr/lib/golang/src/pkg/runtime/netpoll_epoll.c:78
#2  0x00007fff00000004 in ?? ()
#3  0x00007fff58720fd0 in ?? ()
#4  0xffffffff00000080 in ?? ()
#5  0x0000000000000000 in ?? ()

可以使用“docker exec -it [container-id] bash”命令进入正在运行的container

[root@localhost ~]# docker exec -it a972e69ab444 bash
bash-4.1# ls
bin   dev  home  lib64  mnt  pam-1.1.1-17.el6.src.rpm  root      sbin     srv  tmp  var
boot  etc  lib   media  opt  proc                      rpmbuild  selinux  sys  usr
bash-4.1#

docker attach相当于复用了container当前使用的tty,因此在docker attach内执行exit,会导致正在运行的container退出。而docker exec会新建立一个tty,在docker exec中执行exit不会导致container退出。

 

参考资料:
(1)Docker – Enter Running Container with new TTY

如何杀死一个已经detached的screen会话?

如果想杀死一个已经detachedscreen会话,可以使用以下命令:

screen -X -S [session # you want to kill] quit

举例如下:

[root@localhost ~]# screen -ls
There are screens on:
        9975.pts-0.localhost    (Detached)
        4588.pts-3.localhost    (Detached)
2 Sockets in /var/run/screen/S-root.

[root@localhost ~]# screen -X -S 4588 quit
[root@localhost ~]# screen -ls
There is a screen on:
        9975.pts-0.localhost    (Detached)
1 Socket in /var/run/screen/S-root.

可以看到,4588会话已经没有了。

参考资料:
(1)Kill detached screen session

awk数组笔记

1.awk中的数组是一维数组,使用时不用事先声明。第一次使用数组元素时,会自动生成数组元素的值,默认为空字符串""和数字0。请参考以下例子:

Nan:~ nanxiao$ awk 'END {if (arr["A"] == "") print "Empty string"}'
Empty string
Nan:~ nanxiao$ awk 'END {if (arr["A"] == 0) print "Number 0"}'
Number 0

2.awk中的数组是关联数组(associative array),数组下标为字符串。

3.使用for循环可遍历数组下标:
其中访问数组下标的顺序与具体的实现相关。此外,如果在遍历时加入了新的元素,那么程序运行结果是不确定的。

4.使用subscript in array表达式来判断数组是否包含指定的数组下标。如果array[subscript]存在,表达式返回1,反正返回0
注意:使用subscript in array不会创建array[subscript],而if (array[subscript] != "")则会创建array[subscript](如果array[subscript]不存在的话)。

5.删除数组元素:delete array[subscript]

6.split(string, array, fs)使用fs作为字段分隔符(field separator),把字符串string拆分后,传到array数组中。第一个字段保存在array["1"],第二个字段保存在array["2"]…。如果没有指定fs,则使用内置变量FS作为分隔符。

7.多维数组。awk不直接支持多维数组,但可以通过一维数组来模拟。

8.数组元素不能再是数组。

正则表达式笔记

以下笔记来自于《The awk programming language》里《正则表达式》一节。

1.正则表达式的元字符(metacharacters):
\^$.[]|{}*+?

2.基本的正则表达式形式:
a)普通字符(非元字符),像A,就匹配A自己;
b)转义序列(Escape Sequences):\b\f\n\r\t\ddd\c\c表示保留任意字符的字面含义,如\"表示")。
c)取消特殊含义的元字符,像\*,仅表示*本身;
d)^:匹配字符串的开头;
e)$:匹配字符串的结尾;
f).:匹配单个字符;
g)character class[...]complemented character class[^...]。举个例子,[ABC]匹配ABC中的任意一个字符,而[^0-9]匹配任意一个不是数字的字符。在[...][^...]中,除了\,紧随着[出现的^,和在两个字符直间的-3个字符外,其它字符不再具有特殊含义。如[.]则仅匹配一个.

3.运算符:
a)|A|B表示匹配A或者B
b)连接:AB表示匹配A后面紧跟着B
c)A*:匹配0个或多个A
d)A+:匹配1个或多个A
e)A?:匹配0个或1A
f)(r)()仅仅是把正则表达式扩起来。

 

Bash shell进程问题浅析(续)

上文,再来看一下exec这个bash shell内置命令:

exec [-cl] [-a name] [command [arguments]]
              If command is specified, it replaces the shell.  No new process is created.  The arguments become the arguments to command.  If the -l option is supplied, the shell places a dash at the beginning of the zeroth argument passed to command.  This is what login(1) does.  The -c option causes command  to  be executed with an empty environment.  If -a is supplied, the shell passes name as the zeroth argument to the executed command.  If command cannot be executed for some reason,  a  non-interactive  shell  exits,  unless the shell option execfail is enabled, in which case it returns failure.  An interactive shell returns failure if the file cannot be executed.  If command is not specified,  any  redirections  take  effect in the current shell, and the return status is 0.  If there is a redirection error, the return status is 1.  

可以看到,当用exec执行一个命令时,不会产生新的进程,并且这个命令会替换掉当前的bash shell进程。让我们看个例子。在一个终端执行下列命令:

[root@localhost ~]# echo $$
22330
[root@localhost ~]# exec sleep 60

再在另一个终端执行下列命令:

[root@localhost ~]# ps -ef | grep 22330
root     22330 22329  0 05:50 pts/0    00:00:00 sleep 60
root     22361 22345  0 05:52 pts/1    00:00:00 grep --color=auto 22330

可以看到22330号进程变成了sleep 60,而不是bash shell进程了。60秒后,sleep 60进程结束了,终端也退出了:

[root@localhost ~]# ps -ef | grep 22330
root     22363 22345  0 05:56 pts/1    00:00:00 grep --color=auto 22330

最后,通过演示经典的《Shell十三问》《exec跟source差在哪?》一章结尾的例子,再好好理解一下bash shell进程的相关问题:
1.sh

#!/bin/bash
A=B
echo "PID for 1.sh before exec/source/fork:$$"
export A
echo "1.sh: \$A is $A"
case $1 in
    exec)
        echo "using exec..."
        exec ./2.sh ;;
    source)
        echo "using source..."
        . ./2.sh ;;
    *)
        echo "using fork by default..."
        ./2.sh ;;
esac
echo "PID for 1.sh after exec/source/fork:$$"
echo "1.sh: \$A is $A"

2.sh:

#!/bin/bash
echo "PID for 2.sh: $$"
echo "2.sh get \$A=$A from 1.sh"
A=C
export A
echo "2.sh: \$A is $A"

(1)执行“./1.sh fork”:

[root@localhost ~]# ./1.sh fork
PID for 1.sh before exec/source/fork:22390
1.sh: $A is B
using fork by default...
PID for 2.sh: 22391
2.sh get $A=B from 1.sh
2.sh: $A is C
PID for 1.sh after exec/source/fork:22390
1.sh: $A is B

可以看到,由于1.sh脚本(进程ID22390)会新起一个subshell进程去执行2.sh(进程ID22391),所以在2.sh脚本中对A的修改不会影响到1.sh脚本中A的值。

(2)执行“./1.sh source”:

[root@localhost ~]# ./1.sh source  
PID for 1.sh before exec/source/fork:22393
1.sh: $A is B
using source...
PID for 2.sh: 22393
2.sh get $A=B from 1.sh
2.sh: $A is C
PID for 1.sh after exec/source/fork:22393
1.sh: $A is C

可以看到,由于2.sh脚本会在1.sh脚本进程中运行(打印出的进程ID均为22393),所以在2.sh脚本中对A的修改会影响到1.sh脚本中A的值。

(3)执行“./1.sh exec”:

[root@localhost ~]# ./1.sh exec
PID for 1.sh before exec/source/fork:22396
1.sh: $A is B
using exec...
PID for 2.sh: 22396
2.sh get $A=B from 1.sh
2.sh: $A is C

2.sh脚本会在1.sh脚本进程中运行(打印出的进程ID均为22396),同时原有的1.sh脚本进程不会再运行。所以2.sh脚本运行结束后,不会再执行1.sh脚本的命令。

参考资料:
Shell十三问

Bash shell进程问题浅析

bash shell中执行一个命令时,其实是由bash shell fork出一个子进程,然后在这个子进程中运行相应的命令,直至退出。在一个终端执行下列操作:

[root@localhost ~]# echo $$
19954
[root@localhost ~]# sleep 100

在另一个终端执行下列操作:

[root@localhost bin]# ps -ef | grep 19954
root     19954 19353  0 03:01 pts/3    00:00:00 /bin/bash
root     20265 19954  0 04:39 pts/3    00:00:00 sleep 100
root     20267 19354  0 04:39 pts/0    00:00:00 grep --color=auto 19954

可以看到第一个终端的bash shell(进程ID19954fork产生了sleep 100这个进程。

当在bash shell中执行一个bash shell脚本时,其实先会fork出一个subshell子进程,再由这个子进程执行脚本中的命令。举个例子可能会解释的更清楚。

这是一个简单的bash shell脚本(test.sh):

#!/bin/bash
sleep 100

在一个终端执行这个脚本:

[root@localhost ~]# echo $$
19954
[root@localhost ~]# ./test.sh

在另一个终端执行下列操作:

[root@localhost bin]# ps -ef | grep test
root     20309 19954  0 05:01 pts/3    00:00:00 /bin/bash ./test.sh
root     20316 19354  0 05:02 pts/0    00:00:00 grep --color=auto test
[root@localhost bin]# ps -ef | grep 20309
root     20309 19954  0 05:01 pts/3    00:00:00 /bin/bash ./test.sh
root     20310 20309  0 05:01 pts/3    00:00:00 sleep 100
root     20318 19354  0 05:02 pts/0    00:00:00 grep --color=auto 20309

可以看到第一个终端的bash shell(进程ID19954fork产生了test.sh这个进程(进程ID20309),而test.sh这个进程又fork产生了sleep 100这个进程。

接下来要提一下source(“.”命令作用是一样的)这个bash shell内建命令。man source是这样解释的:

.  filename [arguments]
source filename [arguments]
      Read and execute commands from filename in the current shell environment and return the exit status of the last command executed from filename.  If filename does not contain a slash, file names in  PATH  are used to find the directory containing filename.  The file searched for in PATH need not be executable.  When bash is not in posix mode, the current directory is searched if no  file is  found in PATH.  If the sourcepath option to the shopt builtin command is turned off, the PATH is not searched.  If any arguments are supplied, they become the positional parameters when filename  is  executed.  Otherwise the positional parameters are unchanged.  The return status is the status of the last command exited within the script (0 if no commands are executed), and false if filename is not found or cannot be read.

可以看出,“source filename [arguments]”会在当前的shell环境执行文件中的命令,也就是不会产生一个subshell子进程。仍利用test.sh脚本演示一下:

在一个终端执行这个脚本:

[root@localhost ~]# echo $$
19954
[root@localhost ~]# source ./test.sh

在另一个终端执行下列操作:

[root@localhost bin]# ps -ef | grep test
root     20349 19354  0 05:24 pts/0    00:00:00 grep --color=auto test
[root@localhost bin]# ps -ef | grep 19954
root     19954 19353  0 03:01 pts/3    00:00:00 /bin/bash
root     20345 19954  0 05:24 pts/3    00:00:00 sleep 100
root     20347 19354  0 05:24 pts/0    00:00:00 grep --color=auto 19954

可以看到并没有test.sh这个进程,sleep 100这个进程是由bash shell(进程ID19954)进程直接fork产生的。

参考资料:
Shell十三问

使用screen命令产生并管理多个工作窗口

Unix/Linux下的screen命令一个很重要的功能就是可以在一个物理连接(SSH/telnet)终端(terminal)上创建并管理多个工作窗口(也称之为“virtual terminal”)。达到此目的可以有以下两种方式:

(1)启动screen命令进入一个工作窗口,进行一番操作后,使用ctrl-a d命令离开这个窗口,回到真实的终端。然后在真实终端再次启动screen命令,就可以启动下一个工作窗口。如下例所示:

[root@localhost ~]# screen
[detached from 5362.pts-0.localhost]
[root@localhost ~]# screen
[detached from 5378.pts-0.localhost]
[root@localhost ~]#
[root@localhost ~]# ps -ef | grep -i SCREEN
root      5362     1  0 06:19 ?        00:00:00 SCREEN
root      5378     1  0 06:20 ?        00:00:00 SCREEN
root      5394  5334  0 06:20 pts/0    00:00:00 grep --color=auto -i SCREEN
[root@localhost ~]# screen -ls
There are screens on:
        5378.pts-0.localhost    (Detached)
        5362.pts-0.localhost    (Detached)
2 Sockets in /var/run/screen/S-root.
[root@localhost ~]# ps -ef | grep 5378
root      5378     1  0 06:20 ?        00:00:00 SCREEN
root      5379  5378  0 06:20 pts/2    00:00:00 /bin/bash
root      5745  5722  0 21:45 pts/0    00:00:00 grep --color=auto 5378
[root@localhost ~]# ps -ef | grep 5362
root      5362     1  0 06:19 ?        00:00:00 SCREEN
root      5363  5362  0 06:19 pts/1    00:00:00 /bin/bash
root      5749  5722  0 21:50 pts/0    00:00:00 grep --color=auto 5362

可以看到会有两个SCREEN进程(进程号分别为53625378),同时每个进程都有一个/bin/bash的子进程,而这个/bin/bash子进程就是工作窗口(“virtual terminal”)。

(2)利用嵌套screen命令,也就是启动screen命令进入一个工作窗口以后,不会返回真正的终端,而是不断在当前的工作窗口中递归地调用screen命令。如下例所示:

[root@localhost ~]# screen
[detached from 5756.pts-0.localhost]
[root@localhost ~]# ps -ef | grep -i SCREEN
root      5756     1  0 21:52 ?        00:00:00 SCREEN
root      5817  5722  0 21:52 pts/0    00:00:00 grep --color=auto -i SCREEN
[root@localhost ~]# ps -ef | grep 5756
root      5756     1  0 21:52 ?        00:00:00 SCREEN
root      5757  5756  0 21:52 pts/1    00:00:00 /bin/bash
root      5772  5756  0 21:52 pts/2    00:00:00 /bin/bash
root      5787  5756  0 21:52 pts/3    00:00:00 /bin/bash
root      5802  5756  0 21:52 pts/4    00:00:00 /bin/bash
root      5819  5722  0 21:53 pts/0    00:00:00 grep --color=auto 5756

可以看到这种方式只会有一个SCREEN进程,所有的工作窗口(“virtual terminal”)都是由这一个进程产生的。

参考资料:
(1)Why is there only one SCREEN process in nested screen session?
(2)10 Screen Command Examples to Manage Linux Terminals