[apue] 等待子进程的那些事儿
谈到等待子进程,首先想到的就是SIGCHLD信号与wait函数族,本文试图厘清二者的方方面面,以及组合使用时可能不小心掉进去的坑。
1. 首先谈单独使用SIGCHLD的场景。下面是一段典型的代码片段:
#include "../apue.h"
#include <sys/wait.h> #define CLD_NUM 2
static void sig_cld (int signo)
{
pid_t pid = ;
int status = ;
printf ("SIGCHLD received\n");
if (signal (SIGCHLD, sig_cld) == SIG_ERR)
perror ("signal error");
if ((pid = wait (&status)) < )
perror ("wait(in signal) error");
printf ("pid (wait in signal) = %d\n", pid);
} int main ()
{
pid_t pid = ;
__sighandler_t ret = signal (SIGCHLD, sig_cld);
if (ret == SIG_ERR)
perror ("signal error");
else
printf ("old handler %x\n", ret); for (int i=; i<CLD_NUM; ++ i)
{
if ((pid = fork ()) < )
perror ("fork error");
else if (pid == )
{
sleep ();
printf ("child %u exit\n", getpid ());
_exit ();
} sleep ();
} for (int i=; i<CLD_NUM; ++ i)
{
pause ();
printf ("wake up by signal %d\n", i);
} printf ("parent exit\n");
return ;
}
父进程启动了两个子进程,在SIGCHLD信号处理器中调用wait等待已结束的子进程,回收进程信息,防止产生僵尸进程(zombie)。上面的代码会有如下的输出:
old handler 0
child 28542 exit
SIGCLD received
pid (wait in signal) = 28542
wake up by signal 0
child 28543 exit
SIGCLD received
pid (wait in signal) = 28543
wake up by signal 1
parent exit
当然捕获SIGCHLD,也可以使用sigaction接口:
#include "../apue.h"
#include <sys/wait.h> #define CLD_NUM 2
static void sig_cld (int signo, siginfo_t *info, void* param)
{
int status = ;
if (signo == SIGCHLD)
{
if (info->si_code == CLD_EXITED ||
info->si_code == CLD_KILLED ||
info->si_code == CLD_DUMPED)
{
//printf ("child %d die\n", info->si_pid);
if (waitpid (info->si_pid, &status, ) < )
perror ("wait(in signal) error");
printf ("pid (wait in signal) = %d\n", info->si_pid);
}
else
{
printf ("unknown signal code %d\n", info->si_code);
}
}
} int main ()
{
pid_t pid = ;
struct sigaction act;
sigemptyset (&act.sa_mask);
act.sa_sigaction = sig_cld;
act.sa_flags = SA_SIGINFO | SA_NOCLDSTOP;
int ret = sigaction (SIGCHLD, &act, );
if (ret == -)
perror ("sigaction error"); for (int i=; i<CLD_NUM; ++ i)
{
if ((pid = fork ()) < )
perror ("fork error");
else if (pid == )
{
sleep ();
printf ("child %u exit\n", getpid ());
_exit ();
} sleep ();
} for (int i=; i<CLD_NUM; ++ i)
{
pause ();
printf ("wake up by signal %d\n", i);
} printf ("parent exit\n");
return ;
}
输出是一样的。
关于signal与sigaction的区别,有以下几点:
a) 使用sigaction可以避免重新安装信号处理器的问题;
b) 使用sigaction可以在wait之前得知是哪个子进程结束了,这是通过指定SA_SIGINFO标志位,并提供带siginfo_t参数的信号处理器来实现的(info->si_pid就是结束的进程号);
c) 使用sigaction可以获取除子进程结束以外的状态变更通知,例如挂起、继续,默认接收相应通知,除非指定SA_NOCLDSTOP标志。而对于signal而言,没有办法不接收子进程非结束状态的通知(此时调用wait可能会卡死);
d) 使用sigaction可以自动wait已结束的子进程,只要指定SA_NOCLDWAIT标志即可。此时在信号处理器中不用再调用wait函数了。
当使用SA_NOCLDWAIT标志位时,使用systemtap可以观察到子进程还是向父进程发送了SIGCHLD信号的:
30049 cldsig 30048 cldsig 17 SIGCHLD
30050 cldsig 30048 cldsig 17 SIGCHLD
很有可能是系统内部自动wait了相关子进程。
另外在使用SA_NOCLDWAIT时,可以不指定信号处理器,此时sa_sigaction字段可以设置为SIG_DFL。
关于SIGCHLD信号,有以下几点需要注意:
a) 如果在注册信号之前,就已经有已结束但未等待的子进程存在,则事件不会被触发;
b) 可以为SIGCHLD注册一个处理器,也可以忽略该信号(SIG_IGN),忽略时系统自动回收已结束的子进程;
当正常捕获SIGCHLD时,使用systemtap是可以观察到子进程向父进程发送的SIGCHLD信号的:
29877 cldsig 29876 cldsig 17 SIGCHLD
29878 cldsig 29876 cldsig 17 SIGCHLD
29876 cldsig 27771 bash 17 SIGCHLD
当忽略SIGCHLD时,是看不到的,只能看到父进程结束时向bash发送的SIGCHLD信号:
29893 cldsig 27771 bash 17 SIGCHLD
这里注意一下二者在细节处的一点区别。
c) 还有一个SIGCLD信号,在大多数unix like系统中与SIGCHLD表现一致,在某些古老的unix系统上,可能有独特的表现需要注意,这方面请参考 apue 第十章第七节
在我测试的环境上(CentOS 6.7),该信号被定义为SIGCHLD,因此是完全相同的;
关于使用信号等待子进程最后需要谈的一点就是信号的竞争行为,对上面的例子稍加修改,就可以演示一下:
#include "../apue.h"
#include <sys/wait.h> #define CLD_NUM 2
void pid_remove (pid_t pid)
{
printf ("remove pid %u\n", pid);
}
void pid_add (pid_t pid)
{
printf ("add pid %u\n", pid);
} static void sig_cld (int signo)
{
pid_t pid = ;
int status = ;
printf ("SIGCHLD received\n");
if (signal (SIGCHLD, sig_cld) == SIG_ERR)
perror ("signal error");
if ((pid = wait (&status)) < )
perror ("wait(in signal) error");
printf ("pid (wait in signal) = %d\n", pid);
pid_remove (pid);
} int main ()
{
pid_t pid = ;
__sighandler_t ret = signal (SIGCHLD, sig_cld);
if (ret == SIG_ERR)
perror ("signal error");
else
printf ("old handler %x\n", ret); for (int i=; i<CLD_NUM; ++ i)
{
if ((pid = fork ()) < )
perror ("fork error");
else if (pid == )
{
//sleep (3);
printf ("child %u exit\n", getpid ());
_exit ();
} sleep ();
pid_add (pid);
} sleep ();
printf ("parent exit\n");
return ;
}
父进程在启动子进程后需要将它的信息通过pid_add添加到某种数据结构中,当收到SIGCHLD信号后,又通过pid_remove将它从这个数据结构中移出。
在上面的例子中,子进程一启动就退出了,快到甚至父进程还没有来得及执行pid_add就先执行了pid_remove,这必然导致某种问题。
(注意,为了能更好的呈现信号竞争的问题,这里故意在父进程sleep之后调用pid_add),执行结果如下:
old handler 0
child 31213 exit
SIGCLD received
pid (wait in signal) = 31213
remove pid 31213
add pid 31213
child 31214 exit
SIGCLD received
pid (wait in signal) = 31214
remove pid 31214
add pid 31214
parent exit
可以看到,remove总是在add之前执行。而解决方案也很直接,就是在pid_add完成之前,我们需要屏蔽SIGCHLD信号:
for (int i=; i<CLD_NUM; ++ i)
{
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
sigprocmask(SIG_BLOCK, &mask, NULL);
if ((pid = fork ()) < )
perror ("fork error");
else if (pid == )
{
sigprocmask(SIG_UNBLOCK, &mask, NULL);
//sleep (3);
printf ("child %u exit\n", getpid ());
_exit ();
} sleep ();
pid_add (pid);
sigprocmask(SIG_UNBLOCK, &mask, NULL);
}
这里用到了sigprocmask去屏蔽以及解除某种信号的屏蔽。新的代码运行结果如下:
old handler 0
child 31246 exit
add pid 31246
SIGCLD received
pid (wait in signal) = 31246
remove pid 31246
child 31247 exit
SIGCLD received
pid (wait in signal) = 31247
remove pid 31247
add pid 31247
parent exit
可以看到一切正常了,add这次位于remove之前。
总结一下,使用SIGCHLD信号适合异步等待子进程的场景,并且通常搭配wait来回收子进程。
2. 然后谈单独使用wait函数族的场景。典型代码如下:
#include "../apue.h"
#include <sys/wait.h> #define CLD_NUM 2
int main ()
{
pid_t pid = ;
for (int i=; i<CLD_NUM; ++ i)
{
if ((pid = fork ()) < )
perror ("fork error");
else if (pid == )
{
sleep ();
printf ("child %u exit\n", getpid ());
_exit ();
} sleep ();
} int status = ;
for (int i=; i<CLD_NUM; ++ i)
{
if ((pid = wait (&status)) < )
perror ("wait error"); printf ("pid = %d\n", pid);
} printf ("parent exit\n");
return ;
}
与之前场景不同的是,这里父进程同步等待启动的子进程结束。上面的代码会有如下输出:
child 28583 exit
child 28584 exit
pid = 28583
pid = 28584
parent exit
关于wait函数族,需要注意以下几点:
a) wait用于等待任何一个子进程,相当于waitpid(-1, status, 0); 当没有任何子进程存在时,返回-1,errno设置为ECHILD;
b) waitpid相对于wait的优势在于:
i) 可以指定子进程(组)来等待;
ii) 可以捕获子进程除结束以外的其它状态变更通知,如挂起(WUNTRACED)、继续(WCONTINUED)等;
iii) 可以不阻塞的测试某个子进程是否已结束(WNOHANG);
c) wait函数族可被信号中断,此时返回-1,errno设置为EINTR,必要时需要重启wait;
总结一下,使用wait函数族适合同步等待子进程,例如某种命令执行器进程,通常配合waitpid来回收子进程。
3. 最后谈谈混合使用同步wait与异步wait函数族的场景。
其实前面已经提到SIGCHLD要搭配wait使用,但那是异步使用wait的单一场景,而这里讲的混合,是指同时在信号处理器与执行流程中使用wait。
例如bash,它除了在主流程中同步等待前台正在运行的子进程,还必需在信号处理器中异步接收后台运行子进程的状态反馈,这样就不得不混合使用wait。
同步等待某个子进程一般使用waitpid,而在信号处理器中一般使用wait,典型的代码如下所示:
#include "../apue.h"
#include <sys/wait.h>
#include <errno.h> #define CLD_NUM 2 static void sig_cld (int signo)
{
pid_t pid = ;
int status = ;
printf ("SIGCLD received\n");
if (signal (SIGCLD, sig_cld) == SIG_ERR)
perror ("signal error"); if ((pid = wait (&status)) < )
perror ("wait(in signal) error");
else
printf ("pid (wait in signal) = %d\n", pid);
} int main ()
{
pid_t pid = ;
__sighandler_t ret = signal (SIGCLD, sig_cld);
if (ret == SIG_ERR)
perror ("signal error");
else
printf ("old handler %x\n", ret); for (int i=; i<CLD_NUM; ++ i)
{
if ((pid = fork ()) < )
perror ("fork error");
else if (pid == )
{
if (i % == ) {
// simulate background
sleep ();
}
else {
// simulate foreground
sleep ();
} printf ("child %u exit\n", getpid ());
_exit ();
} sleep ();
} int status = ;
printf ("before wait pid %u\n", pid);
if (waitpid (pid, &status, ) < )
printf ("wait %u error %d\n", pid, errno);
else
printf ("wait child pid = %d\n", pid); sleep ();
printf ("parent exit\n");
return ;
}
父进程启动两个子进程,第一个休眠3秒后退出,第二个休眠4秒后退出,由于父进程同步等待的是第二个子进程,因此第二个进程模拟前台进程,第一个进程模拟后台进程。运行输出如下:
old handler 0
before wait pid 2481
child 2480 exit
SIGCLD received
pid (wait in signal) = 2480
wait 2481 error 4
child 2481 exit
SIGCLD received
pid (wait in signal) = 2481
parent exit
此时同步等待的waitpid被信号中断了(EINTR),此种情况下,我们需要重启waitpid:
int status = ;
while () {
printf ("before wait pid %u\n", pid);
if (waitpid (pid, &status, ) < )
{
int err = errno;
printf ("wait %u error %d\n", pid, err);
if (err == EINTR)
continue;
}
else
printf ("wait child pid = %d\n", pid); break;
}
如果因EINTR引发的错误,则重新调用waitpid;否则,退出。新的代码输出如下:
old handler 0
before wait pid 2513
child 2512 exit
SIGCLD received
pid (wait in signal) = 2512
wait 2513 error 4
before wait pid 2513
child 2513 exit
SIGCLD received
wait(in signal) error: No child processes
wait child pid = 2513
parent exit
可以看到两个进程退出时,都收到了SIGCHLD信号,只是前台进程被waitpid优先等待到了,所以信号处理器中的wait返回的ECHILD错误,但是如果还有其它子进程在运行,这里将会在信号处理器的wait中卡死。
之前提到,可以使用SIG_IGN来自动回收子进程,这里试一下使用SIG_IGN来代替sig_cld,看看有什么改观。
old handler 0
before wait pid 2557
child 2556 exit
child 2557 exit
wait 2557 error 10
parent exit
同样的,两个子进程都走了忽略信号,而同步等待的waitpid因没有进程可等返回了ECHILD。因为waitpid是指定进程等待的,所以即使还有其它子进程存在,这个也会返回错误,不会卡死在那里。
相比上面的方法,似乎好了一点,但是因为我们没有安装处理器,所以无从得知哪个后台进程结束了,这并不是我们想到的结果。
之前提到,可以使用sigaction代替signal以获取更多的控制,我们看看换新的方式捕获信号,会不会有一些改变,新的代码逻辑如下:
#include "../apue.h"
#include <sys/wait.h>
#include <errno.h> #define CLD_NUM 2 static void sig_cld (int signo, siginfo_t *info, void* param)
{
int status = ;
if (signo == SIGCHLD)
{
if (info->si_code == CLD_EXITED ||
info->si_code == CLD_KILLED ||
info->si_code == CLD_DUMPED)
{
if (waitpid (info->si_pid, &status, ) < )
err_ret ("wait(in signal) %u error", info->si_pid);
else
printf ("pid (wait in signal) = %d\n", info->si_pid);
}
else
{
printf ("unknown signal code %d\n", info->si_code);
}
}
} int main ()
{
pid_t pid = ;
struct sigaction act;
sigemptyset (&act.sa_mask);
act.sa_sigaction = sig_cld;
act.sa_flags = SA_SIGINFO | SA_NOCLDSTOP;
int ret = sigaction (SIGCHLD, &act, );
if (ret == -)
perror ("sigaction error"); for (int i=; i<CLD_NUM; ++ i)
{
if ((pid = fork ()) < )
perror ("fork error");
else if (pid == )
{
if (i % == ) {
// simulate background
sleep ();
}
else {
// simulate foreground
sleep ();
} printf ("child %u exit\n", getpid ());
_exit ();
} sleep ();
} int status = ;
while () {
printf ("before wait pid %u\n", pid);
if (waitpid (pid, &status, ) < )
{
int err = errno;
printf ("wait %u error %d\n", pid, err);
if (err == EINTR)
continue;
}
else
printf ("wait child pid = %d\n", pid); break;
} sleep ();
printf ("parent exit\n");
return ;
}
运行输出如下:
before wait pid 2585
child 2584 exit
pid (wait in signal) = 2584
wait 2585 error 4
before wait pid 2585
child 2585 exit
wait(in signal) 2585 error: No child processes
wait child pid = 2585
parent exit
结果与使用signal很相似,但是因为在信号处理器中我们能明确的知道是哪个子进程终结了,使用的是waitpid而不是wait,所以即使还有其它子进程未结束,也不会在信号处理器的waitpid中卡住。
结论是无论使用signal还是sigaction,同步等待的waitpid总比SIGCHLD信号处理器中的wait(xxx)具有更高的优先级。
当然,这个前提是在父进程同步waitpid之前,子进程还没有结束;如果要等待的子进程先结束了,SIGCHLD当然先被执行,这种情况下,建议先使用sigprocmask屏蔽SIGCHLD信号,然后在waitpid之前解除屏蔽。虽然不能保证完全解决信号竞争的问题,也能极大的缓解此种情况,即使出现了信号竞争,导致同步等待的waitpid返回ECHILD,我们也能从这些错误码中得知发生的事情,不会出现卡死的情况。
出于好奇,我们看一下改使用SIG_IGN后的运行效果:
before wait pid 2613
child 2612 exit
child 2613 exit
wait 2613 error 10
parent exit
与使用signal时并无二致,仍然是忽略信号占了上风。
结论是无论使用signal还是sigaction,当忽略SIGCHLD信号时,信号优先于wait被忽略。
出于同样的原因,这种方式我们并不采纳。之前提到,sigaction还有一种高级的忽略SIGCHLD的方式,即指定SA_NOCLDWAIT标志位,同时给信号处理器指定SIG_DFL,这种情况下,我们看看输出会有什么变化:
before wait pid 2719
child 2718 exit
child 2719 exit
wait 2719 error 10
parent exit
可以看到,与使用SIG_IGN并无二致。
与SIG_IGN不同的是,我们可以为SIGCHLD提供一个处理器,虽然在此信号处理器中无需再次等待子进程,但是我们拥有了获取子进程信息的能力,相对而言,比SIG_IGN更有用一些。新的输出如下:
before wait pid 2737
child 2736 exit
pid (auto wait in signal) = 2736
wait 2737 error 4
before wait pid 2737
child 2737 exit
pid (auto wait in signal) = 2737
wait 2737 error 10
parent exit
可以看到,同步waitpid仍然返回ECHILD,显然是信号更具有优先级。
好了,到这里就全明了了,对于混合使用同步与异步wait的应用来说,最佳的方法应该是同步waitpid等待前台进程,异步使用sigaction注册SIGCHLD信号处理器等待后台进程,且不设置SA_NOCLDWAIT标志位。
在处理器中也应使用waitpid等待子进程,如返回ECHILD错误,证明该子进程是前台进程,已经被同步wait掉了,不需要后续处理;否则作为后台进程处理。
没有多少人会有机会写一个shell,但是并非只有shell才有混合使用同步、异步等待子进程的场景,考虑下面个场景:
#include "../apue.h"
#include <unistd.h>
#include <sys/wait.h> #define PAGER "${PAGER:-more}" #define USE_SIG 2
static void sig_cld (int signo)
{
pid_t pid = ;
int status = ;
printf ("SIGCLD received\n");
if (signal (SIGCLD, sig_cld) == SIG_ERR)
perror ("signal error"); if ((pid = wait (&status)) < )
perror ("wait(in signal) error"); printf ("pid (wait in signal) = %d\n", pid);
} void install_handler (__sighandler_t h)
{
__sighandler_t ret = signal (SIGCLD, h);
if (ret == SIG_ERR)
perror ("signal error");
else
printf ("old handler %x\n", ret);
} int main (int argc, char *argv[])
{
int n = ;
#if USE_SIG == 1
install_handler (sig_cld);
#elif USE_SIG == 2
install_handler (SIG_IGN);
#endif char line[MAXLINE] = { };
FILE *fpin = NULL, *fpout = NULL;
if (argc != )
err_quit ("usage: ppage <pathname>"); fpin = fopen (argv[], "r");
if (fpin == NULL)
err_sys ("can't open %s", argv[]); fpout = popen (PAGER, "w");
if (fpout == NULL)
err_sys ("popen %s error", PAGER); while (fgets (line, MAXLINE, fpin) != NULL) {
if (fputs (line, fpout) == EOF)
err_sys ("fputs error to pipe");
} if (ferror (fpin))
err_sys ("fgets error"); int ret = pclose(fpout);
if (ret == -)
err_sys ("pclose error");
else
printf ("worker return %d\n", ret); return ;
}
程序运行后打开参数指定的文件,读取并将它通过管道传递给more命令。随后通过pclose等待more命令结束。这期间为了保证其它子进程(假设存在)能正常回收,使用SIG_IGN注册了SIGCHLD信号。
运行程序,退出more后有如下输出:
pclose error: No child processes
pclose失败了,这是为什么呢?答案就是前面说过的,pclose内部存在着一个隐式的waitpid在同步等待more子进程,而此时SIGCHLD被注册为忽略取得了优先权,导致waitpid失败从而导致pclose返回错误。
可见,当程序中存在pclose、system等隐式wait调用时,如果同时需要SIGCHLD信号处理,则一定不能 (a)注册为忽略SIG_IGN (b)通过sigaction注册并设置SA_NOCLDWAIT标志位,否则相应的调用会失败。
最后补充一点,我们发现同步等待的waitpid没有被中断的情况只在忽略信号的时候产生,而之前也证明了忽略信号时,系统压根不产生SIGCHLD信号,这两者似乎到现在是对上了…… :)
[apue] 等待子进程的那些事儿的更多相关文章
- 父进程等待子进程结束 waitpid wait
我们一直在强调一个概念就是进程是一个程序执行的实例,是内核在虚拟概念下创建的实体,它实例化的体现在用户态就是程序代码和代码使用的变量(存储空间),在内核态就是内核为我们每个进程所保存的数据结构(状态信 ...
- Linux下利用fork()创建子进程并使父进程等待子进程结束
int status; pid_t t = fork(); if(t){ waitpid(t, &status, 0); }else{ system("vi temp ...
- 如何测试Linux 中的wait函数能不能等待子进程的子进程?
#include <stdio.h> #include <stdlib.h> int main() { pid_t pid = fork(); switch(pid) { : ...
- linux 进程学习笔记-等待子进程结束
<!--[if !supportLists]-->Ÿ <!--[endif]-->等待子进程结束 pid_t waitpid(pid_t pid, int *stat_loc, ...
- Windows批处理 调用程序后 不等待子进程 父进程继续执行命令
从DOS过来的老鸟应该都知道批处理,这个功能在WINDOWS中仍然保留着.批处理 说白了就是把一系列DOS命令写在一个文本文件里,然后把这个文件命名为XXX.bat(WINXP以后的系统也可以命名为* ...
- fork新建进程——父进程等待子进程结束
#include <sys/types.h>#include<sys/wait.h>#include<unistd.h>#include<stdio.h> ...
- [apue] 使用 Ctrl+S停止输出而不用挂起前台进程
之前一直知道使用 Ctrl+Z 挂起前台进程来阻止进程运行,之后可以再通过 shell 的作业控制 (jobs / fg N) 来将后台进程切换为前台,从而继续运行. 最近学到一种新的方法,对于不停有 ...
- apue 外传
先上目录 chapter 3 [apue] dup2的正确打开方式 chapter 10 [apue] 等待子进程的那些事儿 chapter 14 [apue] 使用文件记录锁无法实现父子进程交互执行 ...
- [apue] linux 文件访问权限那些事儿
前言 说到 linux 上的文件权限,其实我们在说两个实体,一是文件,二是进程.一个进程能不能访问一个文件,其实由三部分内容决定: 文件的所有者.所在的组: 文件对所有者.组用户.其它用户设置的权限访 ...
随机推荐
- git clone命令简介
git clone: 正如上图,当我们打开终端的情况下,默认我们所在的目录是在/home/shiyanlou的,大家可以在终端输入以下命令把目录切换到桌面cd /home/Desktop这个时候输入 ...
- 如何直接访问WEB-INF下列文件
<servlet> <servlet-name>HE</servlet-name> <jsp-file>/WEB-INF/u_member/Login. ...
- PostgreSQL模式匹配的方法 LIKE等
PostgreSQL 提供了三种实现模式匹配的方法:传统 SQL 的 LIKE 操作符.SQL99 新增的 SIMILAR TO 操作符. POSIX 风格的正则表达式.另外还有一个模式匹配函数 su ...
- ItemsPanelTemplate
用以定义集合控件的容器外观,如ListBox,Combox 等等使用一个自定义的ListBox用以说明,其默认外观是上下排列,这里修改成横向排列 <Window.Resources> &l ...
- Lexer的设计--下(5)
一个礼拜之后我终于从成都回来了, 从今天开始更新会恢复... 一点小的改进 写lex()的时候距离我上一次写已经一个礼拜了, 所以我回顾了一下之前的代码, 发现还是有瑕疵. 比如考虑到一个较短的程序, ...
- Web应用程序和网站的区别
1项目就是一个应用程序.在VS中查看的时候,项目中建立的一般处理程序,有两个文件,网站只有一个.写个代码测试,发现在代码层次上没有2再有就是项目中的一般处理程序有命名空间,而网站中的没有.WEB网站每 ...
- WPF获取控件内部的ScrollViewer,并控制ScrollViewer操作
//获取内部 ScrollViewer方法 public static T FindVisualChild<T>(DependencyObject obj) where T : Depe ...
- requirejs教程(一):基本用法
介绍 RequireJS是一个非常小巧的JavaScript模块载入框架,是AMD规范最好的实现者之一.最新版本的RequireJS压缩后只有14K,堪称非常轻量.它还同时可以和其他的框架协同工作,使 ...
- Win8 Metro(C#)数字图像处理--2.39二值图像投影
原文:Win8 Metro(C#)数字图像处理--2.39二值图像投影 [函数名称] 二值图像投影 ImageProjection(WriteableBitmap src) ...
- oracle rac 修改dbid和dbname
=======================修改数据库dbid====================== 1.查看目前数据库dbid以及dbname [root@rac01 ~]# su - or ...