Category: Linux

解决socket在execl之后被自动继承的问题

本文同步自:https://zohead.com/archives/close-socket-after-execl/ Linux下的进程在创建子进程(fork)后,子进程会自动继承父进程的文件描述符,这种机制在没有 execl 运行时一般都没有问题,而且也比较方便在子进程中直接使用父进程中打开的文件描述符,但如果子进程通过 execl 类函数运行新的程序时,这些继承的文件描述符却没有被关闭,导致 execl 之后的程序中也有父进程中文件描述符的拷贝,有些情况就会出现错误。 由于 socket 也在这些文件描述符范围内,本文简单说下解决 socket 被自动继承的几种方法。 首先是测试程序,头文件就没列出来了。 父进程和子进程分别 sleep 10秒钟,方便外部查看打开的文件描述符,编译运行程序,使用 lsof 命令就可以看到子进程在 execl 之后的 sleep 进程中也打开了父进程的 socket 描述符(UDP连接),这不是我们想要的效果滴: [root@localhost ~]# lsof -c sleep COMMAND     PID    USER   FD        TYPE  DEVICE SIZE/OFF   NODE      NAME sleep               5750  root       cwd      DIR     8,1           4096           8145         /root sleep               5750  root      rtd         DIR     8,1           4096           2                / sleep               5750  root      txt         REG    8,1           27880         456104    /bin/sleep sleep               5750  root     mem    REG    8,1           155696       301801    /lib64/ld-2.12.so sleep               5750  root     mem    REG    8,1           1912928    301802    /lib64/libc-2.12.so sleep               5750  root     mem    REG    8,1           99158752  391655   /usr/lib/locale/locale-archive sleep               5750  root     0u        CHR    136,1      0t0                4              /dev/pts/1 sleep               5750  root     1u        CHR    136,1      0t0               4               /dev/pts/1 sleep               5750  root     2u        CHR    136,1      0t0               4               /dev/pts/1 sleep               5750  root     3u        IPv4    15022      0t0             UDP         *:5555 解决办法有如下几种: 1、比较死板的方法,在子进程 fork 之后关闭所有的 socket 描述符,在 execl 之前加上:     int fd = 0, fdtablesize = 0;     for (fd = 0, fdtablesize = getdtablesize(); fd < fdtablesize; fd++) {         // close all socket handle inherited from parent process         if (isfdtype(fd, S_IFSOCK)) {             close(fd);         }     }     这种处理比较彻底,即使子进程没有 execl 也会关闭继承的 socket 句柄,如果要关闭从父进程继承的所有句柄,去掉 isfdtype 判断即可。 2、使用 fcntl 为父进程 socket 设置 O_CLOEXEC 标志,表示此 socket 在 execl 之前自动关闭(注意并非在 fork 之后),在 socket 创建完成之后加上:     ret = fcntl(sock, F_GETFD);     ret = fcntl(sock, F_SETFD, ret | FD_CLOEXEC); 3、Linux 2.6.27 之后创建 socket 时开始支持 SOCK_CLOEXEC 参数,与上面的效果相似,只要改创建 socket 的地方:     sock = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP);     另外 Linux 2.6.27 之后创建 socket 另外支持 SOCK_NONBLOCK 参数,如果设置此参数,socket 自动创建为非阻塞式。 以上三种办法均在 RHEL 6.1 64位系统上编译测试通过,有任何问题欢迎交流,玩的开心 ^_^

rsync在 Linux/cygwin/msys 环境下的备份性能对比

本文博客链接:https://zohead.com/archives/rsync-performance-linux-cygwin-msys/ rsync是一个开源免费的文件同步和备份工具,可用于本地备份,本地与远程服务器之间的备份,可以实现增量和差异备份,而且由于比较好的算法,在文件备份速度上也相对其它一些文件备份工具有明显的优势。 但 rsync 一直以来没有 Windows 下的原生客户端,都是基于 cygwin 环境实现,实际备份性能会受一些影响,近日看到 rsync 的 基于 MSYS 的 Win32 原生客户端已经被 port 出来,故简单做下性能对比测试。 测试环境: rsync服务器为 RHEL5 Linux 64bit,8个SATA盘的RAID0做下层存储,采用单千兆网络和千兆交换机 rsync客户端为:RHEL5 Linux 64bit,Windows 2003 Enterprise 32bit 测试时 rsync 均通过匿名方式访问,不经过SSH做用户验证,由于考虑到测试的 rsync 客户端的系统盘速度有瓶颈,客户端文件读写都通过内存文件系统来实现(Linux 上使用 tmpfs,Windows 上使用 ImDisk 模拟内存盘)。 使用同样的客户端主板分别在 Linux 和 Windows 内存中产生 1.5GB 的测试文件,然后通过 rsync 客户端进行备份到服务器(写操作)和从服务器上恢复(读操作)的操作。 备份命令示例: rsync -hv x.dat 192.168.1.125::rsync0/ 测试软件列表: 标准 Linux rsync 客户端(RHEL 5 系统自带) cygwin rsync 客户端 MSYS rsync 客户端 http://sourceforge.net/projects/mingw/files/MSYS/Extension/rsync/ RsyncWin32 客户端 http://sourceforge.net/projects/rsyncwin32/ 测试结果:   测试软件 写性能(MB/s) 读性能(MB/s) Linux rsync 105.27 105.28 cygwin rsync 76.22 64.49 MSYS rsync 7.98 8.14 RsyncWin32 76.72(出现错误) 38.50(出现错误) 从测试结果看,由于 rsync 本身面向类 Linux 环境开发,在 Linux 系统中有着非常好的性能,cygwin rsync 与 Linux 相比有一定差距,但实际使用中还是比较稳定的,而 MSYS rsync 还处于测试阶段,虽然没有出现备份错误,但在千兆网络环境下性能非常差,RsyncWin32 则相对而言问题比较多,备份过程中甚至会出现备份错误。 综上看来,目前在 Windows 上使用 cygwin rsync 做备份客户端仍然算是比较好的解决方案,MSYS rsync 的问题可以啥时候有空再看看咯。

Linux下出现 pam_get_item 的原因分析

本文博客链接:https://zohead.com/archives/linux-pam-get-item-error-library/ 近日在将一个 RHEL6 上的服务程序移植到老的 Fedora Core 2 Linux 上时出现了一些问题,该程序的功能为预先加载几个动态库,这几个动态库中再需要根据不同的需求做 PAM 用户验证操作,程序在 RHEL6 上编译和使用都没有问题,换到 Fedora Core 2 环境之后,编译时一切正常,但在运行时出现比较奇怪的 pam_get_item 错误: pam: PAM [dlerror: /lib/security/../../lib/security/pam_env.so: undefined symbol: pam_get_item] pam: PAM [dlerror: /lib/security/../../lib/security/pam_unix.so: undefined symbol: pam_get_item] pam: PAM [dlerror: /lib/security/../../lib/security/pam_succeed_if.so: undefined symbol: pam_get_item] … 程序所加载的动态库中使用的 PAM 配置文件 /etc/pam.d/system-auth 里就用到了 pam_env.so、pam_unix.so、pam_succeed_if.so 等几个 PAM 模块。 但实际系统中还有很多服务都使用 /etc/pam.d/system-auth 这个 PAM 配置来进行用户验证,都能正常工作,因此做下简单分析。 由于此服务程序太复杂,就打定写个简单的模拟程序来进行分析。 1、动态库部分(checkpam.c): PAM 验证的实现,调用 PAM 函数进行验证,具体 PAM 函数的调用就不详细写出了。 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <security/pam_appl.h> int check_pam(const char * user, const char * pass) {     int ret = -1;     pam_start("system-auth", user, …, …);     …     // 实际 PAM 代码省略,成功返回0,失败返回1 //     …     pam_end(…);     return ret; } 和实际服务程序一致,通过 system-auth PAM 配置进行验证,编译生成动态库: cc -c checkpam.c cc -shared -fPIC -lpam -o libcheckpam.so checkpam.o ldd libcheckpam.so,查看此动态库的依赖列表: [root@linux root]# ldd libcheckpam.so         linux-gate.so.1 =>  (0x00c67000)         libpam.so.0 => /lib/libpam.so.0 (0x0025b000)         libc.so.6 => /lib/tls/libc.so.6 (0x0041d000)         libdl.so.2 => /lib/libdl.so.2 (0x00153000)         /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x00876000) 2、主程序部分(pamtest.c): 加载动态库,调用 PAM 进行用户验证,比较丑陋,只需能验证,HOHO。 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <dlfcn.h> int main(int argc, char ** argv) {     int ret = 0;     void * lpso = NULL;     int (* lpfunc)(const char *, const char *) = NULL;     lpso = dlopen("./libcheckpam.so", RTLD_LAZY);     if (lpso == NULL) {         printf("Library load failed: %s.\n", dlerror());         return 1;     }     lpfunc = dlsym(lpso, "check_pam");     if (lpfunc == NULL) {         printf("Load symbol failed: %s.\n", dlerror());         dlclose(lpso);         return 2;     }     ret = lpfunc(argv[1], argv[2], NULL);     dlclose(lpso);     printf("pam ret: %d.\n", ret);     return ret; } 功能很简单,dlopen 加载刚才生成的 libcheckpam.so,然后 dlsym 找到 check_pam 函数,接着调用函数进行用户验证。 编译程序: cc -o pamtest pamtest.c -ldl ldd pamtest,查看程序的依赖列表,可以看到此时已不需要 pam 库,和实际使用的服务程序的实现方式一致了: [root@linux root]# ldd pamtest         linux-gate.so.1 =>  (0x00631000)         libdl.so.2 => /lib/libdl.so.2 (0x009ac000)         libc.so.6 => /lib/tls/libc.so.6 (0x0088f000)         /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x00876000) […]

Linux advisory and mandatory lock with fcntl

本文博客链接:https://zohead.com/archives/linux-fcntl-advisory-mandatory-lock/ 近日小温下APUE,发现Linux下的 fcntl 实现强制锁的功能好像都没试验过,简单做个测试。 首先用 fcntl 实现建议锁(Advisory locking),比较简单,贴个最简单的代码: 关键的几句已高亮显示,用 F_SETLKW 等待写的锁,很好测试,分别开一个终端运行一次就可以测试出来。 备注: Linux 同时实现了 POSIX 的 fcntl 锁函数,BSD 的 flock 函数,SVR4 的 lockf 函数,这些默认都是建议锁。 这个简单的实现只是建议锁,需要每个对文件的操作的进程都遵循同样的锁操作才能起作用,这些进程称为合作进程(Cooperative processes),但实际使用时会有例外的情况,如果另外开一个终端窗口直接写 test.file 文件,会发现可以直接写,因为这种写没有锁操作,不是合作进程,这种情况就需要用强制锁(Mandatory locking)了。 强制锁的实际代码和上面的完全一样,不过在 Linux 上需要做一些改动: 1、挂载文件系统时需要加 mand 参数在文件系统上启用强制锁支持,比较新的 Linux kernel 里已经基本在所有文件系统上都实现了; 2、去掉程序的组执行权限; 3、增加程序的设置组ID权限。 第二项和第三项在普通情况下实际上是自相矛盾的,所以 Linux 就用这种特殊情况就表示启用强制锁(Mandatory locking)了。 备注: 1、使用 BSD 的 flock 函数时不能使用强制锁,见 Linux kernel 源码下 mandatory-locking.txt 中的说明:       Mandatory locks can only be applied via the fcntl()/lockf() locking       interface - in other words the System V/POSIX interface. BSD style       locks using flock() never result in a mandatory lock. 2、如果一个文件被某进程强制锁住,另一个进程通过 creat 函数或者 open 时加 O_TRUNC 参数,都会导致函数调用失败; 3、强制锁在不同的 UNIX 及 类UNIX 系统中实现不同,第二个备注中的 O_TRUNC 参数便是 Linux kernel 和 HP-UX 不同的地方; 4、tmpfs、nfs 也支持强制锁,可以用这两个文件系统方便测试; 5、删除文件(unlink操作)再重新创建文件,会导致强制锁失效; 6、Linux kernel 的强制锁的实现并不完全可靠(感觉白写了,哈哈,实际情况还要做处理),见 man fcntl 的说明。 强制锁的实际操作: mount -t tmpfs -o size=10m,mand tmpfs /mnt cp test /mnt cd /mnt chmod g-x test chmod g+s test 运行 test 程序,同时开另一个终端,直接用 echo 读写 test 文件,会发现强制锁已经起作用。