程序是一段可执行的代码,是在硬盘上的一个文件,进程则是程序执行的一个实例,每个进程都有自己的地址空间。
进程的三种状态及其转换:
进程的ID(PID)是唯一标识一个进程,是一个正整数。PPID为父进程的进程ID。
init是所有进程的祖先,称为祖先进程,进程ID为1。
获得ID的函数:
pid_t getpid(void);//获得本进程ID
pid_t getppid(void);//获得父进程ID
pid_t getuid(void);//获得当前用户ID
创建一个子进程:
fork();
函数原型:
pid_t fork(void);
函数作用:
创建一个子进程,创建失败返回-1,创建成功,在父进程中返回子进程ID,在子进程中返回0。子进程复制父进程地址空间(除了代码段),与父进程共享代码段。两个进程都从fork()处往下执行。父子进程的执行先后顺序不确定(我在红帽系统下调试,绝大多数情况是子进程先执行)。
vfork();
函数原型:
pid_t vfork(void);
函数作用:
创建一个子进程,创建失败返回-1,创建成功,在父进程中返回子进程ID,在子进程中返回0。子进与父进程共享地址空间。两个进程都从vfork()处往下执行。子进程必须要显示调用exit(),否则会有段错误。子进程先于父进程执行,子进程执行完,CPU才会调度父进程。
exec函数族
用被执行的程序完全替换调用它的程序。
函数原型:
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 *const argv[]);
int execvp(const char *file, char *const argv[]);
终止一个进程:
exit();
函数原型:
void _exit(int status);
函数作用:
用来正常终结目前进程的执行,并把参数status(子进程结束状态)返回给父进程,在停止进程之前,要检查文件的打开情况,并把文件缓冲区中的内容写回文件才停止进程。。
_exit();
函数原型:
void _exit(int status);
函数作用:
不会返回参数给父进程,父进程可以由wait函数取得子进程结束状态,该函数直接使进程停止,清除其使用的内存,并清除缓冲区中内容。
进程终止的两种情况:
父进程比子进程先退出:
子进程会被祖先进程接收,并且会在后台执行,当子进程结束,祖先进程负责处理退出状态。这样的进程被称为守护进程。守护进程是一个在后台运行并且不受任何终端控制的进程。
创建守护进程:
1、创建子进程,关闭父进程。
2、设置文件掩码。
3、设置新的会话,脱离当前会话和终端的控制。
4、改变当前工作目录。
5、关闭标准输出、标准输入、标准错误。
6、重定向标准输出、标准输入、标准错误。
源代码如下:
int daemonize(void)
{
pid_t pid = fork();//创建子进程
if (pid < 0)
{
perror("fork");
return -1;
}
else if (pid > 0)
{
exit(0);//关闭父进程
}
else
{
umask(0);//设置文件掩码
if(setsid() < 0)//设置新的会话
{
perror("setsid");
return -1;
}
if(chdir("/") < 0)//改变当前工作目录为根目录
{
perror("chdir");
return -1;
}
//关闭标准输出、标准输入、标准错误
close (STDIN_FILENO);
close (STDOUT_FILENO);
close (STDERR_FILENO);
//重定向标准输出、标准输入、标准错误
open ("/dev/null",O_RDONLY);
open ("/dev/null",O_RDWR);
open ("/dev/null",O_RDWR);
}
return 0;
}
运行程序时,在后面加上取地址符&也可以使进程跑在后台,但会受当前终端控制。
子进程比父进程先退出:
如果父进程没有处理子进程的结束状态,就会产生僵尸进程,使用ps命令查看时,进程后有<defunct>标志。此进程占内存很小,但是会占用一个进程ID,由于系统的进程ID是有限的,如果僵尸进程过多,系统可能无法再产生新的进程。所以僵尸进程的危害还是很大的,而很多人都不会注意处理。
解决办法:父进程要处理子进程退出状态,使用wait函数。
函数原型:
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
wait(int *status)函数的参数为结束状态,一般不关心的话可填NULL,此函数的返回值是子进程ID,如果没有子进程,立刻返回错误,如果有,会一直等待,处于阻塞状态,直到等到子进程退出,此函数一次只能处理一个子进程,用起来不是很方便。
waitpid(pid_t pid, int *status, int options)函数的参数更多,第一个参数可以指定处理的子进程的ID,也可以设为通用(-1),第三个参数可指定是否是使用阻塞方式,可以设为非阻塞方式,没有检测到推出的子程序,就直接往下执行,使用更反方便。
进程控制对于系统编程非常重要,我整理的也仅仅是皮毛,欢迎大家一起交流讨论。