进程
一、fork系统调用
q 包含头文件 <sys/types.h> 和 <unistd.h>
q 函数功能:创建一个子进程
q 函数原型
pid_t fork(void);
q 参数:无参数。
q 返回值:
q 如果成功创建一个子进程,对于父进程来说返回子进程ID
q 如果成功创建一个子进程,对于子进程来说返回值为0
q 如果为-1表示创建失败
fork 系统调用注意点
q fork系统调用之后,父子进程将交替执行。
q 如果父进程先退出,子进程还没退出那么子进程的父进程将变为init进程。(注:任何一个进程都必须有父进程)
q 如果子进程先退出,父进程还没退出,那么子进程必须等到父进程捕获到了子进程的退出状态才真正结束,否则这个时候子进程就成为僵进程。
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
#defineERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
intmain(int argc, char *argv[])
{
signal(SIGCHLD, SIG_IGN);
printf("before fork pid =%d\n", getpid());
pid_t pid;
pid = fork();
if (pid == -1)
ERR_EXIT("forkerror");
if (pid > 0)
{
printf("this is parentpid=%d childpid=%d\n", getpid(), pid);
sleep(100);
}
else if (pid == 0)
{
printf("this is childpid=%d parentpid=%d\n", getpid(), getppid());
}
return 0;
}
二、写时复制copy on write
q 如果多个进程要读取它们自己的那部分资源的副本,那么复制是不必要的。
q 每个进程只要保存一个指向这个资源的指针就可以了。
q 如果一个进程要修改自己的那份资源的“副本”,那么就会复制那份资源。这就是写时复制的含义
三、fork之后父子进程共享文件
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
#defineERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
intmain(int argc, char *argv[])
{
signal(SIGCHLD, SIG_IGN);
printf("before fork pid =%d\n", getpid());
int fd;
fd = open("test.txt",O_WRONLY);
if (fd == -1)
ERR_EXIT("openerror");
pid_t pid;
pid = fork();
if (pid == -1)
ERR_EXIT("forkerror");
if (pid > 0)
{
printf("this is parentpid=%d childpid=%d\n", getpid(), pid);
write(fd, "parent",6);
sleep(3);
}
else if (pid == 0)
{
printf("this is childpid=%d parentpid=%d\n", getpid(), getppid());
write(fd, "child",5);
}
return 0;
}
四、fork和vfork
q 在fork还没实现copy on write之前。Unix设计者很关心fork之后立刻执行exec所造成的地址空间浪费,所以引入了vfork系统调用。
q vfork有个限制,子进程必须立刻执行_exit或者exec函数。
q 即使fork实现了copy on write,效率也没有vfork高,但是我们不推荐使用vfork,因为几乎每一个vfork的实现,都或多或少存在一定的问题。
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
#defineERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int gval = 100;
intmain(int argc, char *argv[])
{
signal(SIGCHLD, SIG_IGN);
printf("before fork pid = %d\n",getpid());
pid_t pid;
pid = fork();
if (pid == -1)
ERR_EXIT("forkerror");
if (pid > 0)
{
sleep(1);
printf("this is parentpid=%d childpid=%d gval=%d\n", getpid(), pid, gval);
sleep(3);
}
else if (pid == 0)
{
gval++;
printf("this is childpid=%d parentpid=%d gval=%d\n", getpid(), getppid(), gval);
}
return 0;
}
因为fork创建的进程为copy on write,故其不共享gval变量
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
#defineERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int gval= 100;
intmain(int argc, char *argv[])
{
signal(SIGCHLD, SIG_IGN);
printf("before fork pid =%d\n", getpid());
pid_t pid;
pid= vfork();
if (pid == -1)
ERR_EXIT("forkerror");
if (pid > 0)
{
sleep(1);
printf("this is parentpid=%d childpid=%d gval=%d\n", getpid(), pid, gval);
sleep(3);
}
else if (pid == 0)
{
gval++;
printf("this is childpid=%d parentpid=%d gval=%d\n", getpid(), getppid(), gval);
exit(0);
}
return 0;
}
五、atexit
q atexit可以注册终止处理程序,ANSI C规定最多可以注册32个终止处理程序。
q 终止处理程序的调用与注册次序相反
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
#defineERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
voidmy_exit1(void)
{
printf("my exit1 ...\n");
}
voidmy_exit2(void)
{
printf("my exit2 ...\n");
}
intmain(int argc, char *argv[])
{
atexit(my_exit1);
atexit(my_exit2);
//_exit(0); 不能输出
exit(0);
}
六、fork深入理解
代码段+数据段+堆栈段+PCB
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
#defineERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
intmain(int argc, char *argv[])
{
signal(SIGCHLD, SIG_IGN);
printf("before fork pid =%d\n", getpid());
pid_t pid;
pid = fork();
if (pid == -1)
ERR_EXIT("forkerror");
if (pid > 0)
{
printf("this is parentpid=%d childpid=%d\n", getpid(), pid);
sleep(3);
}
else if (pid == 0)
{
printf("this is childpid=%d parentpid=%d\n", getpid(), getppid());
}
return 0;
}
理解printf("before fork pid = %d\n", getpid());为什么没有输出两次
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
#defineERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
intmain(int argc, char *argv[])
{
fork();
fork();
fork();
printf("ok\n");
return 0;
}
七、替换进程映像
intexecve(const char *filename, char *const argv[], char *const envp[])
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
#defineERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int gval= 100;
intmain(int argc, char *argv[])
{
signal(SIGCHLD, SIG_IGN);
printf("before fork pid =%d\n", getpid());
pid_t pid;
pid = vfork();
if (pid == -1)
ERR_EXIT("forkerror");
if (pid > 0)
{
printf("this is parentpid=%d childpid=%d gval=%d\n", getpid(), pid, gval);
}
else if (pid == 0)
{
char *const args[] ={"ps", NULL};
execve("/bin/ps",args, NULL);
gval++;
printf("this is childpid=%d parentpid=%d gval=%d\n", getpid(), getppid(), gval);
}
return 0;
}
八 exec关联函数
q 包含头文件<unistd.h>
q 功能用exec函数可以把当前进程替换为一个新进程。exec名下是由多个关联函数组成的一个完整系列,头文件<unistd.h>
q 原型
int execl(const char *path, const char*arg, ...);
int execlp(const char *file, const char*arg, ...);
int execle(const char *path, const char*arg,
..., char * const envp[]);
int execv(const char *path, char *constargv[]);
int execvp(const char *file, char *constargv[]);
q 参数
q path参数表示你要启动程序的名称包括路径名
q arg参数表示启动程序所带的参数
q 返回值:成功返回0,失败返回-1
q execl,execlp,execle(都带“l”)的参数个数是可变的,参数以一个空指针结束。
q execv和execvp的第二个参数是一个字符串数组,新程序在启动时会把在argv数组中给定的参数传递到main
q 这些函数通常都是用execve实现的,这是一种约定俗成的做法,并不是非这样不可。
q 名字最后一个字母是“p”的函数会搜索PATH环境变量去查找新程序的可执行文件。如果可执行文件不在PATH定义的路径上,就必须把包括子目录在内的绝对文件名做为一个参数传递给这些函数
man exec查看相关用法
九 SIGCHLD
q 当子进程退出的时候,内核会向父进程发送SIGCHLD信号,子进程的退出是个异步事件(子进程可以在父进程运行的任何时刻终止)
q 子进程退出时,内核将子进程置为僵尸状态,这个进程称为僵尸进程,它只保留最小的一些内核数据结构,以便父进程查询子进程的退出状态。
q 父进程查询子进程的退出状态可以用wait/waitpid函数
wait
q 头文件<sys/types.h>和<sys/wait.h>
q 函数功能:当我们用fork启动一个进程时,子进程就有了自己的生命,并将独立地运行。有时,我们需要知道某个子进程是否已经结束了,我们可以通过wait安排父进程在子进程结束之后。
q 函数原型
q pid_twait(int *status)
q 函数参数
q status:该参数可以获得你等待子进程的信息
q 返回值:
q 成功等待子进程函数返回等待子进程的ID
q wait系统调用会使父进程暂停执行,直到它的一个子进程结束为止。
q 返回的是子进程的PID,它通常是结束的子进程
q 状态信息允许父进程判定子进程的退出状态,即从子进程的main函数返回的值或子进程中exit语句的退出码。
q 如果status不是一个空指针,状态信息将被写入它指向的位置
宏定义 | 描述 |
WIFEXITED(status) | 如果子进程正常结束,返回一个非零值 |
WEXITSTATUS(status) | 如果WIFEXITED非零,返回子进程退出码 |
WIFSIGNALED(status) | 子进程因为捕获信号而终止,返回非零值 |
WTERMSIG(status) | 如果WIFSIGNALED非零,返回信号代码 |
WIFSTOPPED(status) | 如果子进程被暂停,返回一个非零值 |
WSTOPSIG(status) | 如果WIFSTOPPED非零,返回一个信号代码 |
waitpid
q函数原型:
pid_twaitpid(pid_t pid, int *status,int options)
q参数:
q status:如果不是空,会把状态信息写到它指向的位置
qoptions:允许改变waitpid的行为,最有用的一个选项是WNOHANG,它的作用是防止waitpid把调用者的执行挂起
q 返回值:如果成功返回等待子进程的ID,失败返回-1
对于waitpid的p i d参数的解释与其值有关:
q pid == -1等待任一子进程。于是在这一功能方面waitpid与wait等效。
q pid >0 等待其进程I D与p i d相等的子进程。
q pid == 0 等待其组I D等于调用进程的组I D的任一子进程。换句话说是与调用者进程同在一个组的进程。
q pid <-1 等待其组I D等于p i d的绝对值的任一子进程。
wait与waitpid的区别
q 在一个子进程终止前, wait 使其调用者阻塞,而waitpid 有一选择项,可使调用者不阻塞。
q waitpid并不等待第一个终止的子进程—它有若干个选择项,可以控制它所等待的特定进程。
q 实际上wait函数是waitpid函数的一个特例。
僵进程
q 当一个子进程结束运行时,它与其父进程之间的关联还会保持到父进程也正常地结束运行或者父进程调用了wait才告终止。
q 进程表中代表子进程的数据项是不会立刻释放的,虽然不再活跃了,可子进程还停留在系统里,因为它的退出码还需要保存起来以备父进程中后续的wait调用使用。它将称为一个“僵进程”。
如何避免僵进程
q 调用wait或者waitpid函数查询子进程退出状态,此方法父进程会被挂起。
q 如果不想让父进程挂起,可以在父进程中加入一条语句:signal(SIGCHLD,SIG_IGN);表示父进程忽略SIGCHLD信号,该信号是子进程退出的时候向父进程发送的。
#include<unistd.h>
#include<sys/stat.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<fcntl.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
#defineERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
intmain(int argc, char *argv[])
{
pid_t pid;
pid = fork();
if (pid == -1)
ERR_EXIT("forkerror");
if (pid == 0)
{
sleep(3);
printf("this ischild\n");
//exit(100);
abort();
}
int ret;
printf("this is parent\n");
int status;
//ret = wait(&status);
//ret = waitpid(-1, &status, 0);
ret = waitpid(pid, &status, 0);
printf("ret = %d pid = %d\n",ret, pid);
if (WIFEXITED(status))
printf("child exitednormal exit status=%d\n", WEXITSTATUS(status));
/*else
printf("child exited abnormal\n");
*/
else if (WIFSIGNALED(status))
printf("child exitedabnormal signal number=%d\n", WTERMSIG(status));
else if (WIFSTOPPED(status))
printf("child stopedsignal number=%d\n", WSTOPSIG(status));
return 0;
}
system
q 功能:system()函数调用“/bin/sh -c command”执行特定的命令,阻塞当前进程直到command命令执行完毕
q 原型:
int system(const char *command);
q 返回值:
如果无法启动shell运行命令,system将返回127;出现不能执行system调用的其他错误时返回-1。如果system能够顺利执行,返回那个命令的退出码。
q system函数执行时,会调用fork、execve、waitpid等函数。
#include<unistd.h>
#include<sys/stat.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<fcntl.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
#defineERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
intmy_system(const char *command);
intmain(int argc, char *argv[])
{
//system("ls -l | wc -w");
my_system("ls -l | wc -w");
return 0;
}
intmy_system(const char *command)
{
pid_t pid;
int status;
if (command == NULL)
return 1;
if ((pid = fork()) < 0)
status = -1;
else if (pid == 0)
{
execl("/bin/sh","sh", "-c", command, NULL);
exit(127);
}
else
{
while (waitpid(pid,&status, 0) < 0)
{
if (errno == EINTR)
continue;
status = -1;
break;
}
}
return status;
}
一、什么是守护进程
q 守护进程是在后台运行不受控端控制的进程,通常情况下守护进程在系统启动时自动运行
q 守护进程的名称通常以d结尾,比如sshd、xinetd、crond等
创建守护进程的步骤
q 调用fork(),创建新进程,它会是将来的守护进程
q 在父进程中调用exit,保证子进程不是进程组组长
q 调用setsid创建新的会话期
q 将当前目录改为根目录
q 将标准输入、标准输出、标准错误重定向到/dev/null
#include<unistd.h>
#include<sys/stat.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<fcntl.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
#defineERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int setup_daemon(intnochdir, int noclose);
intmain(int argc, char *argv[])
{
setup_daemon(1, 1);
//daemon(1, 1);
printf("test ...\n");
for (;;) ;
return 0;
}
intsetup_daemon(int nochdir, int noclose)
{
pid_t pid;
pid = fork();
if (pid == -1)
ERR_EXIT("forkerror");
if (pid > 0)
exit(EXIT_SUCCESS);
setsid();
if (nochdir == 0)
chdir("/");
if (noclose == 0)
{
int i;
for (i=0; i<3; ++i)
close(i);
open("/dev/null",O_RDWR);
dup(0);
dup(0);
}
return 0;
}
intdaemon(int nochdir, int noclose);
q 功能:创建一个守护进程
q 参数:
q nochdir:=0将当前目录更改至“/”
q noclose:=0将标准输入、标准输出、标准错误重定向至“/dev/null”