对于同一个文件,虽然每次读写的数据量很小,但是如果每次读写的位置很随机,那么读写的效率也是很低的,大家都知道。
但是有一种情况,可能不太被注意,虽然是顺序读写,依然很慢;现在有一个进程顺序写一个文件,可能每秒能写100多MB,但是,如果有100个类似的进程顺序写100个不同的文件,虽然表面上都是在顺序写,但是对于磁盘来讲,需要不断地切换磁头到不同的文件,其实也已经表现为随机写了。
DevOps
对于同一个文件,虽然每次读写的数据量很小,但是如果每次读写的位置很随机,那么读写的效率也是很低的,大家都知道。
但是有一种情况,可能不太被注意,虽然是顺序读写,依然很慢;现在有一个进程顺序写一个文件,可能每秒能写100多MB,但是,如果有100个类似的进程顺序写100个不同的文件,虽然表面上都是在顺序写,但是对于磁盘来讲,需要不断地切换磁头到不同的文件,其实也已经表现为随机写了。
$ gettext -d coreutils total
练习:
for (( expr1 ; expr2 ; expr3 )) ; do list ; done
1 2 3 4 |
$ for((i=0; i<3;i++))do echo $i;done 0 1 2 |
for name [ in word ] ; do list ; done
1 2 3 4 |
$ for i in {1..3};do echo $i; done 1 2 3 |
select name [ in word ] ; do list ; done
1 2 3 4 5 6 7 |
$ select m in "menu1" "menu2";do if [[ $m == "menu1" ]] ; then echo "you are right"; break;else echo "please retry";fi; done 1) menu1 2) menu2 #? 2 please retry #? 1 you are right |
case word in [ [(] pattern [ | pattern ] … ) list ;; ] … esac
1 2 |
$ case a in (a | b) echo ok;; esac ok |
1 |
( cd ~; ls ) |
1 |
files=$(ls) |
1 |
((i++)) |
1 2 |
j=$((i+1)) #等同于 j=$(($i+1)) |
1 |
arr=( $(ls) ) |
1 |
str=$(ls) |
1 |
arr=(ls) |
摘自: http://tldp.org/HOWTO/Secure-Programs-HOWTO/avoid-vfork.html
The portable way to create new processes in Unix-like systems is to use the fork(2) call. BSD introduced a variant called vfork(2) as an optimization technique. In vfork(2), unlike fork(2), the child borrows the parent’s memory and thread of control until a call to execve(2V) or an exit occurs; the parent process is suspended while the child is using its resources. The rationale is that in old BSD systems, fork(2) would actually cause memory to be copied while vfork(2) would not. Linux never had this problem; because Linux used copy-on-write semantics internally, Linux only copies pages when they changed (actually, there are still some tables that have to be copied; in most circumstances their overhead is not significant). Nevertheless, since some programs depend on vfork(2), recently Linux implemented the BSD vfork(2) semantics (previously vfork(2) had been an alias for fork(2)).
翻译:
在类Unix系统中, 可移植的创建一个新进程的方式是调用fork(2)。BSD中作为一种优化技术引入了一个叫做vfork(2) 的变体。vfork(2) 和fork(2) 不同,它在退出或者调用execve(2V)之前是作为父进程的一个线程存在的,和父进程共用内存空间;而在子进程使用这些资源时,父进程是被挂起的。之所以会有vfork(2),是因为在老的BSD系统中,fork(2)将导致一次真实的内存拷贝,而vfork(2)则不会。Linux中从来没有过这个问题;因为,Linux内部使用的是写时复制(copy-on-write)的机制,Linux仅仅在页被修改时才复制(事实上,仍然会有一些表被复制;在大多数情况下,这没有太大影响)。然而,因为一些程序依赖了vfork(2),最近Linux也实现了BSD的vfork(2)机制(以前的vfork(2)是fork(2)的别名)。
There are a number of problems with vfork(2). From a portability point-of-view, the problem with vfork(2) is that it’s actually fairly tricky for a process to not interfere with its parent, especially in high-level languages. The not interfering” requirement applies to the actual machine code generated, and many compilers generate hidden temporaries and other code structures that cause unintended interference. The result: programs using vfork(2) can easily fail when the code changes or even when compiler versions change.
For secure programs it gets worse on Linux systems, because Linux (at least 2.2 versions through 2.2.17) is vulnerable to a race condition in vfork()’s implementation. If a privileged process uses a vfork(2)/execve(2) pair in Linux to execute user commands, there’s a race condition while the child process is already running as the user’s UID, but hasnt entered execve(2) yet. The user may be able to send signals, including SIGSTOP, to this process. Due to the semantics of vfork(2), the privileged parent process would then be blocked as well. As a result, an unprivileged process could cause the privileged process to halt, resulting in a denial-of-service of the privileged process' service. FreeBSD and OpenBSD, at least, have code to specifically deal with this case, so to my knowledge they are not vulnerable to this problem. My thanks to Solar Designer, who noted and documented this problem in Linux on the
`security-audit” mailing list on October 7, 2000.
The bottom line with vfork(2) is simple: don’t use vfork(2) in your programs. This shouldn’t be difficult; the primary use of vfork(2) is to support old programs that needed vfork’s semantics.
其它参考: http://linux.about.com/library/cmd/blcmdl2_vfork.htm
重点:
1. fork不会挂起父进程, vfork会挂起父进程,直到调用execve或_exit
2. 一般来讲,vfork都是会和execve成对出现的
3. vfork中不应该调用exit
4. 当一个特权进程使用vfork来派生一个非特权进程时,在vfork设置为非特权进程之后,调用execve之前,非特权用户给该进程发送一个STOP信号,将可能会挂起特权进程,所以,尽量不要使用vfork
strace是用来跟踪系统调用的,这里只研究一个问题:
在一个系统调用返回之前,系统调用的参数是否会被显示出来。
测试1:
1 2 |
<?php sleep(20000); |
strace 跟踪的部分信息:
1 2 3 4 5 |
munmap(0xb7d7e000, 4096) = 0 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 rt_sigaction(SIGCHLD, NULL, {SIG_DFL}, 8) = 0 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 nanosleep({10000, 0}, <unfinished ...> |
我们发现,系统调用nanosleep的第一个参数显示出来了,nanosleep的函数签名如下:
1 |
int nanosleep(const struct timespec *req, struct timespec *rem); |
通过对该系统调用的了解,我们发现,第一个参数是要sleep多长时间,而且是const的,不会被修改,所以,在系统调用开始就确定了,所以可以直接显示出来,而第二个参数的含义是: 如果该系统调用因为接收到信号而被迫返回,则第二个参数为该系统调用还剩余的sleep的时间,这个时间只有到该系统调用完成才能确定的,所以,不能立即显示出来。
再举个例子吧:
比如connect系统调用:
1 2 3 |
# strace nc 10.23.33.55 234 ... connect(3, {sa_family=AF_INET, sin_port=htons(234), sin_addr=inet_addr("10.23.33.55")}, 16 <unfinished ...> |
上面是一个不存在的IP地址,所以连接不上的,导致connect阻塞,我们发现,在connect返回之前,我们已经知道3个参数信息(实际上一共也就3个参数),参数的显示能给我们很有用的信息(IP、Port)。
在比如poll系统调用:
1 2 |
<?php file_get_contents("http://10.22.33.44/"); |
跟踪结果:
1 2 3 4 5 6 7 |
#strace test.php ... socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3 fcntl64(3, F_GETFL) = 0x2 (flags O_RDWR) fcntl64(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0 connect(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("10.22.33.44")}, 16) = -1 EINPROGRESS (Operation now in progress) poll( <unfinished ...> |
poll的函数签名如下:
1 |
int poll(struct pollfd *ufds, unsigned int nfds, int timeout); |
我们发现poll有3个参数,但是在poll返回之前,一个参数信息都没有给出,这是因为第一个参数的值需要等到poll返回才能确定,如果我们strace发现一个已经运行这的进程在poll时阻塞了,将很难知道是哪个文件描述符上阻塞的。
你提供了一批接口,很多使用方在调用,虽然每个调用方都有相应的标识,但是,存在这样一种情况,调用方自己都不知道多少地方在调用,这样的话,如果需要通知调用方修改一些东西的时候,很有可能修改不全。
接口定义上规定将使用接口的代码的地址写到接口的参数里面; 当然,这个代码的地址如何标识需要考虑一下的:
接口参数规定: caller=__FILE__
tokyotyrant自有的master<->master, master->slave 模式已经不能满足业务的需要,现在期望可以随意复制,选择性复制。
1. 我们可以直接分析ulog来实现
2. 我们可以参考tokyotyrant的复制协议,通过socket来复制,或者使用C来实现,或者使用自己熟悉的语言实现
3. tokyotyrant提供了一个tcrmgr的工具,其中的repl子命令可以帮助我们通过socket复制数据,我们就可以使用自己熟悉的语言,将输出的信息解析后处理,同步到自己想同步到的地方去
这里仅仅实现了两个命令的解析。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
<?php define("PUT", "10"); define("OUT", "20"); while(!feof(STDIN)) { $str = trim(fgets(STDIN)); if (!$str) continue; $arr = explode("\t", $str); parse_record($arr[3]); } exit; function parse_record($str) { $str = str_replace(" ", "", $str); if (strlen($str) < 4) return false; $magic = substr($str, 0, 2); $cmd = substr($str, 2, 2); if ($magic != "C8") return false; switch($cmd) { case OUT: $ksiz = hexdec(substr($str, 4, 8)) * 2; $key = pack("H*", substr($str, 12, $ksiz)); echo "out\t", $key ,"\n"; break; case PUT: $ksiz = hexdec(substr($str, 4, 8)) * 2; $key = pack("H*", substr($str, 20, $ksiz)); $vsiz = hexdec(substr($str, $ksiz + 18, 8)) * 2; $val = pack("H*", substr($str, $ksiz + 20, $vsiz)); echo "put\t", $key , "\t", $val, "\n"; break; default: } return true; } |
使用方法:
tcrmgr repl -port 4242 -ts 1345999492000000 -sid 1234 -ph 10.71.6.28|php repl.php
记录格式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
struct record{ int magic; // 0xc8 int cmd; // put: 0x10; delete 0x20; ... ... } struct put_record{ int magic; // 0x80 int cmd; // 0x10 int32 ksiz; char *key; int32 vsiz; char *val; } struct delete_record{ int magic; // 0x80 int cmd; // 0x20 int32 ksiz; char *key; } |