开始一个新进程
你可以启动一个程序从另一个程序的内部,所以创建一个进程可以使用system库函数。
#include <stdlib.h>
int system (const char *string);
这个system方法启动一个命令并将string传递给它,然后等待它结束。这个命令的执行有点像 “$ sh -c string”被传递给shell。如果shell不能执行这个命令system函数返回127,如果错误则返回 -1,否则system函数返回命令结束代码。
example: #include <stdlib.h>
#include <stdio.h>
int main()
{
printf(“Running ps with system/n”);
system(“ps ax”);
printf(“Done./n”);
exit(0);
}
Replacing a Process Image
这里有一个以exec开头的相关的方法组。它们的不同之处就是他们开始的进程和提供给程序的参数。一个exec方法会代替当前的进程然后产生一个新的指定进程。你可以使用exec方法去传递一个程序的执行到另一个。exec的方法组比system有效的多,因为新程序开始后原程序不再运行了。
#include <unistd.h>
char **environ;
int execl(const char *path, const char *arg0, ..., (char *)0);
int execlp(const char *file, const char *arg0, ..., (char *)0);
int execle(const char *path, const char *arg0, ..., (char *)0, char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
这些方法归类为两种,execl,execlp和execle有一些可变数量的参数,以一个null指针结束。execv和execvp以一个字符串作为第二个参数。这两种情况下,新的程序开始时将出现在argv数组中的参数传递给main。这些方法中通常会使用execve。
这些以“p”结尾的方法,他们将查询PATH环境变量去查找新的程序执行文件。如果在这个路径中找不到可执行程序,那么就需要传递文件的绝对路径和描述符给方法。这个全局变量environ可以将值给新程序环境。 在方法execle和execve中的另外一个参数可以传递一个字符串数组给新程序作为环境变量。如果你想要开始ps程序,你可以从这六个exec方法组中选择一个,如下:
/* Example of an argument list */
/* Note that we need a program name for argv[0] */
char *const ps_argv[] = {“ps”, “ax”, 0};
/* Example environment, not terribly useful */
char *const ps_envp[] = {“PATH=/bin:/usr/bin”, “TERM=console”, 0};
/* Possible calls to exec functions */
execl(“/bin/ps”, “ps”, “ax”, 0); /* assumes ps is in /bin */
execlp(“ps”, “ps”, “ax”, 0); /* assumes /bin is in PATH */
execle(“/bin/ps”, “ps”, “ax”, 0, ps_envp); /* passes own environment */
execv(“/bin/ps”, ps_argv);
execvp(“ps”, ps_argv);
execve(“/bin/ps”, ps_argv, ps_envp);
复制一个进程
你可以通过使用fork来创建一个新线程,这个方法复制当前的进程,在进程表中创建一个新实体并且拥有和当前进程一样的属性。这个进程等价于原进程,执行同样的代码但是有自己的数据空间和环境和文件描述符。
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
fork返回一个新的子进程的PID给父进程,也就是调用这个的函数的进程。新进程在原进程的基础上继续执行,但有一个例外,这个子进程调用fork返回的是0。如果fork失败了,就返回-1,这个原因可恩嗯是由于父进程所拥有子进程的个数限制,或者空间内存问题。
一个关于fork的典型应用:
pid_t new_pid;
new_pid = fork();
switch(new_pid) {
case -1 : /* Error */
break;
case 0 : /* We are child */
break;
default : /* We are parent */
break;
}
等待进程
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *stat_loc);
这个wait函数引起一个父进程暂停直到其中的一个子进程停止了。这个函数返回子进程的PID,这个子进程可能已经终止了。status信息允许父进程去决定子进程的退出status,这个值从main返回或者之前传递的。如果stat_loc不是一个空指针,这个status信息将会被写到指向的地方。
你可以进一步了解这些状态信息在sys/wait.h中的宏,如下表:
WIFEXITED(stat_val) Nonzero if the child is terminated normally.
WEXITSTATUS(stat_val) If WIFEXITED is nonzero, this returns child exit code.
WIFSIGNALED(stat_val) Nonzero if the child is terminated on an uncaught signal.
WTERMSIG(stat_val) If WIFSIGNALED is nonzero, this returns a signal number.
WIFSTOPPED(stat_val) Nonzero if the child has stopped.
WSTOPSIG(stat_val) If WIFSTOPPED is nonzero, this returns a signal number.
例子:
child_pid = wait(&stat_val);
printf(“Child has finished: PID = %d/n”, child_pid);
if(WIFEXITED(stat_val))
printf(“Child exited with code %d/n”, WEXITSTATUS(stat_val));
else
printf(“Child terminated abnormally/n”);
僵尸进程
使用fork去创建进程是非常有用的,但是你必须和子进程保持联系。一个子进程结束的条件是与之相关的父进程要么也终止了那么就调用了wait。这个子进程加入到进程表所以它不会立刻就被释放。即使它不再活动了,这个子进程仍然存在系统中,因为它的exit代码需要被存储,以为它的父进程调用wait。那么这个子进程就会编程一个僵尸进程。
一个进程在调用exit命令结束自己的生命的时候,其实它并没有真正的被销毁,而是留下一个称为僵尸进程(Zombie)的数据结构(系统调用exit, 它的作用是使进程退出,但也仅仅限于将一个正常的进程变成一个僵尸进程,并不能将其完全销毁)。在Linux进程的状态中,僵尸进程是非常特殊的一种,它 已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集,除此之外, 僵尸进程不再占有任何内存空间。它需要它的父进程来为它收尸,如果他的父进程没安装SIGCHLD信号处理函数调用wait或waitpid()等待子进 程结束,又没有显式忽略该信号,那么它就一直保持僵尸状态,如果这时父进程结束了,那么init进程自动会接手这个子进程,为它收尸,它还是能被清除的。 但是如果父进程是一个循环,不会结束,那么子进程就会一直保持僵尸状态,这就是为什么系统中有时会有很多的僵尸进程。(来自于百度百科)
在Linux系统中还有另一个方法我们可以调用去等待子进程,叫做waitpid,你可以使用它来等待一个指定的进程终止。
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *stat_loc, int options);
pid参数指向子进程需要等待的那个进程的PID,如果返回-1,waitpid将返回任意子进程的信息。像wait,它会将状态校信息写入到stat_loc中。options参数允许你修改waitpid的行为,最有用的参数就是WNOHANG,它阻止调用进程被挂载起来(如果没有任何已经结束的子进程则马上返回, 不予以等待)。其他参数和wait一样。所以你可以用附近成去检查一个指定的子进程是否结束了,像
waitpid(child_pid, (int *) 0, WNOHANG); 返回0,则说明这个子进程没有终止或结束,返回-1时繁盛错误或设置错误。
信号
信号是一个事件由Linux和UNIX系统应对一些状态产生,由一个进程获取并做出一些动作。我们使用raise去表明产生一个信号,用catch表示接收到一个信号。 信号名字定义在signal.h中,他们以SIG开始,表括在如下表中:
SIGABORT *Process abort
SIGALRM Alarm clock
SIGFPE *Floating-point exception
SIGHUP Hangup
SIGILL *Illegal instruction
SIGINT Terminal interrupt
SIGKILL Kill (can’t be caught or ignored)
SIGPIPE Write on a pipe with no reader
SIGQUIT Terminal quit
SIGSEGV *Invalid memory segment access
SIGTERM Termination
SIGUSR1 User-defined signal 1
SIGUSR2 User-defined signal 2
如果一个进程接收到以上的一个信号并没有去第一时间的catch它,那么这个进程会立刻结束掉。
一些额外的信号:
SIGCHLD Child process has stopped or exited.
SIGCONT Continue executing, if stopped.
SIGSTOP Stop executing. (Can’t be caught or ignored.)
SIGTSTP Terminal stop signal.
SIGTTIN Background process trying to read.
SIGTTOU Background process trying to write.
SIGCHLD可以被用来管理子进程,默认它是忽略的。剩余的信号都会导致进程接收到便停止,除了SIGCONT,SIGCONT造成进程恢复。
程序可以控制信号通过使用库函数。
#include <signal.h>
void (*signal(int sig, void (*func)(int)))(int);
需要获取或忽略的信号被做为sig参数,这个func方法发生是在指定的信号被接收到的情况下,这个方法必须有一个单一的信号int参数。signal方法返回一个同类型的方法,由前面提到的控制信号的方法值,或以下两个:
SIG_IGN Ignore the signal.
SIG_DFL Restore default behavior.
例如
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void ouch(int sig)
{
printf(“OUCH! - I got signal %d/n”, sig);
(void) signal(SIGINT, SIG_DFL);
}
int main()
{
(void) signal(SIGINT, ouch);
while(1) {
printf(“Hello World!/n”);
sleep(1);
}
}
发送信号
一个进程可以发送一个信号给另一个进程包括它自己,通过使用kill函数。这个函数可能失败如果这个程序没有发送这个信号的权限。
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
kill函数发送指定的信号sig给指定pid的进程。返回0则说明成功。发送信号的进程必须有足够的权限,通常这是意味着两个进程必须有一个用户ID。kill函数失败则返回-1,然后设置errno。
Signals提供一个有用的alarm时钟设备。这个alarm方法可以被进程使用并安排一个SIGALRM信号在一段时间后。
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
这个alarm函数安排SIGALRM信号在几秒后传递。0值会取消掉所有突出的alarm请求。在这个信号接收到之前就取消这个alram的话,这个alarm会被从新安排。每一个进程只可以有一个alarm。alarm函数返回这个alarm将被发送的剩余的秒数,-1则表示失败。
#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
static int alarm_fired = 0;
void ding(int sig)
{
alarm_fired = 1;
}
int main()
{
pid_t pid;
printf(“alarm application starting/n”);
pid = fork();
switch(pid) {
case -1:
/* Failure */
perror(“fork failed”);
exit(1);
case 0:
/* child */
sleep(5);
kill(getppid(), SIGALRM);
exit(0);
}
/* if we get here we are the parent process */
printf(“waiting for alarm to go off/n”);
(void) signal(SIGALRM, ding);
pause(); // pause, which simply causes the program to suspend execution until a signal occurs
if (alarm_fired)
printf(“Ding!/n”);
printf(“done/n”);
exit(0);
}
一个全面的信号接口
#include <signal.h>
int sigaction(int sig, const struct sigaction *act, struct sigaction *oact);
这个sigaction结构体用于定义接收到指定信号sig后做出的动作,他定义在signal.h中丙炔至少含有以下的成员:
void (*) (int) sa_handler /* function, SIG_DFL or SIG_IGN
sigset_t sa_mask /* signals to block in sa_handler
int sa_flags /* signal action modifiers
这个sigaction方法设置与信号sig相关的行为。如果oact不为空,sigaction函数则将前面的action写入到指定的地方去。如果act是null,代表着所有的信号都会处理,如果act不为空,那么就处理指定的信号。0,成功,-1,失败。
在结构体sigaction类型的参数act中,sa_handler 指向一个方法当接收到信号的时候调用。和signal函数的方法func参数类似。你可以使用指定的值SIG_IGN和SIG_DFL给sa_handler 表示忽略或者被默认的存储。sa_mask参数指定一个一个信号集,在sa_handler被调用之前加入得到该进程的信号标识中。这些信号会被中断也不用被传递到进程中。它可以阻止它的handler在结束之前结构其他信号。
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void ouch(int sig)
{
printf(“OUCH! - I got signal %d/n”, sig);
}
int main()
{
struct sigaction act;
act.sa_handler = ouch;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGINT, &act, 0);
while(1) {
printf(“Hello World!/n”);
sleep(1);
}
}
信号集
头文件signal.h定义了sigset_t类型和一些用于管理信号集的函数。这些集合被用在sigaction和其他函数中用来改变进程接收到信号后的行为。
#include <signal.h>
int sigaddset(sigset_t *set, int signo); //向set中增加一个信号
int sigemptyset(sigset_t *set); //初始化一个信号集为空
int sigfillset(sigset_t *set); //初始化一个信号集去包含所有定义的信号。
int sigdelset(sigset_t *set, int signo); //从set中删除信号signo
int sigismember(sigset_t *set, int signo); //查看信号signo是否在set中,返回1表示是,0表示不是,-1则信号不能错,错误。
这些方法返回0表示成功,-1则表示错误。如果指定的信号不能用,唯一的错误是EINVAL。
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
sigprocmask根据how参数有多种方式可以改变进程信号标识。如果set不为空,新的信号标识会被传递到,以前的信号标识会被写入到oset中。,how可以取的值如下:
SIG_BLOCK The signals in set are added to the signal mask.
SIG_SETMASK The signal mask is set from set.
SIG_UNBLOCK The signals in set are removed from the signal mask.
int sigpending(sigset_t *set); //函数返回在送往进程的时候被阻塞挂起的信号集合
int sigsuspend(const sigset_t *sigmask); //sigsuspend 函数将进程的信号屏蔽字设置为 sigmask 指向的值。在捕捉到一个信号或发生了一个会终止该进程的信号之前,该进程被挂起。如果捕捉到一个信号而且从该信号处理程序返回,则sigsuspend返 回,在返回之前,将进程的信号屏蔽字设置为调用sigsuspend之前的值。
sigaction Flags //P490