Linux系统编程知识在运维排错中的应用

1.    谁偷了我的磁盘空间?

发现大小为10G的/home分区磁盘空间使用了9.8G,但是通过du得到的大小却是5G. 另外的4.8G空间哪里去了呢?

df -Th /home

Filesystem    Type    Size  Used Avail Use% Mounted on

/dev/sda3      ext3     10G   9.8G  0.2G  98% /home

du -hs /home

5.0G    /home/

du和df都不太可能出错,文件系统也没有损坏的迹象,那问题出在哪儿呢?查看unlink系统调用的手册页:

unlink()  deletes  a  name from the filesystem. If that name was the last link to a file and no processes have the file open the file is deleted
and the space it was using is made available for reuse.

If the name was the last link to a file but any processes still have the file open the file  will  remain  in  existence  until  the  last  file
descriptor referring to it is closed.
可以看出,如果一个文件被删除了,但是还有其他hard link,或者有进程打开它,那么磁盘空间不会被释放.
如何找出这样的文件呢?

lsof | grep deleted
#COMMAND     PID      USER   FD      TYPE             DEVICE     SIZE       NODE    NAME
testsvr      10126    root   3w      REG               8,3    5153960755    2801561 /home/curuwong/nohup.out (deleted)

nohup.out这个文件名字虽然从/home/curuwong目录消失了,但是所占据的磁盘空间仍然没有被释放,因为有个叫做testsvr的程序还在打开着这个文件。

du得到的空间占用情况和df的结果差距明显的情形,还有一种原因,那就是某个原本有内容的目录被作为挂载点mount上其他分区,于是那个目录下原本的内容就看不到了,但是它们都还在那里,在占用着空间。你看不见的东西,不意味着它们就不存在。^_^

2. wget监听udp端口?(对,你没看错)

我们某业务上有个叫做virusscanner服务,它监听udp端口9999.这个服务进程会不断地通过system函数启动wget通过http去拉取一些东西(OK,我知道你想说使用libcurl会更高效).
virusscanner刚启动时,通过netstat查看端口监听状况:

netstat -lunp | grep :9999
udp        0      0 0.0.0.0:9999                0.0.0.0:*                             3913/virusscanner

然而一段时间之后:

udp        0      0 0.0.0.0:9999                0.0.0.0:*                               857/wget

从常识出发,我们想象不出通过HTTP下载东西的wget会和UDP扯上什么关系. 那为什么会出现上面的诡异情况呢?

这里主要有两点:
1). 父子进程间的描述符继承
2). 进程PID轮转.

system函数主要做的事情,就是启动一个程序并等待其执行完毕,这主要涉及两个系统调用: fork和exec系列
查看open(2)系统调用的手册页:
The  new file descriptor is set to remain open across an execve(2)
默认情况下,在父进程打开的文件描述符,在子进程中依然保持打开。
这样的结果是,virusscanner打开并监听的udp端口(socket函数的返回值也是文件描述符),在子进程wget中也是处于打开状态,可以被访问。

至于为什么netstat的输出刚开始显示viruscanner,后来显示wget,这就是PID轮转的结果了。从上面的分析我们知道,virusscanner和wget都在”监听”那个udp端口,netstat只是输出pid较小的进程.
在Linux系统下,PID不是无限增长的,它有个最大值,可以通过文件/proc/sys/kernel/pid-max指定。当pid超过这个最大值时,新进程的pid又会从最小的没使用的开始。结果是:某一时刻,子进程的pid会比父进程的pid还小,于是netstat里面的输出,看到的就会是pid较小的子进程wget了。

那对上面的问题,有什么解决办法呢?很简单,修改virusscanner的代码,调用system()函数之前,
在父进程的socket文件描述符上,通过fcntl设置”执行时关闭“标志就可以了。

fcntl(sock_fd, F_SETFD, fcntl(sock_fd, F_GETFD, 0) | FD_CLOEXEC);

3. 有16G内存的空闲系统下,mysql查询竟然报内存不足(数据量小于百万)?

在32位Linux下,启用PAE之后,系统可以管理最多64G的物理内存。然而由于指针大小的限制,
32位程序最多只能寻址4G的内存。要是只有一个mysqld进程,剩下的28G内存都白白浪费了
(这个不完全正确,因为系统会使用一部分内存来作为buffer和cache,对mysql的运行效率还是
有帮助的)
所以在有大内存的机器上,建议安装64位系统.

4. strace: 跟踪程序的系统调用

当你发现一个进程看似挂起了,不知道它是在忙什么的时候,或者发现程序无法像平常那样工作的时候,可以试试strace:
strace -t /path/to/program
strace -t -p process_pid

会显示进程所做的系统调用,在明白每个系统调用的大致含义之后,你大概就能猜想出程序正在做什么,有什么问题。
比如,我们发现有个rsync进程,启动时间为十天前,但现在还在进程列表里面,我们要确定它到底是在干嘛,可以通过ps查到pid,然后:
strace -t -p rsync_pid
发现它根本没有调用读写socket的函数,而是一直在select,等待可读写IO,进一步分析,我们可以知道,是网络连接问题导致rsync挂起。 直接kill掉之后重传就可以了。

又比如,我们不知道本机mysqld要读取的配置文件顺序是什么,但是我们知道my_print_defaults命令能找到和读取正确的文件,这时我们也可以通过strace来找到它:

strace -t ./my_print_defaults mysqld 2>&1 | grep stat

23:35:03 fstat64(3, {st_mode=S_IFREG|0644, st_size=35775, …}) = 0
23:35:03 fstat64(3, {st_mode=S_IFREG|0755, st_size=114145, …}) = 0
23:35:03 fstat64(3, {st_mode=S_IFREG|0755, st_size=47447, …}) = 0
23:35:03 fstat64(3, {st_mode=S_IFREG|0755, st_size=96498, …}) = 0
23:35:03 fstat64(3, {st_mode=S_IFREG|0755, st_size=195447, …}) = 0
23:35:03 fstat64(3, {st_mode=S_IFREG|0755, st_size=1548470, …}) = 0
23:35:03 stat64(“/etc/my.cnf”, {st_mode=S_IFREG|0644, st_size=5106, …}) = 0
23:35:03 fstat64(3, {st_mode=S_IFREG|0644, st_size=5106, …}) = 0
23:35:03 stat64(“/root/.my.cnf”, 0xbf9fc35c) = -1 ENOENT (No such file or directory)
23:35:03 stat64(“/usr/local/mysql-5.0.22/etc/my.cnf”, 0xbf9fc35c) = -1 ENOENT (No such file or directory)

从输出可以看到,mysql需要读取的配置文件顺序为(版本5.0):
/etc/my.cnf => ~/.my.cnf => $base_dir/etc/my.cnf
并且我们知道其只有/etc/my.cnf存在,其他文件都不存在。

总结: 了解一些Linux系统编程知识,能够帮助我们更好地分析各种疑难问题,提高运维工作中的排错能力。

系统编程知识在运维中的应用还有很多,我后续会不断补充。这篇文章只是够抛砖引玉,大家在这方面什么经验,欢迎交流分享。

This entry was posted in Programming, System Administration and tagged , , . Bookmark the permalink.

Leave a Reply