Linux进程简介
- 每个进程都有一个唯一的正数(非零)进程ID (PID),getpid函数返回调用进程的PID,getppid函数返回它的父进程的PID(创建调用进程的进程)。
- 父进程通过调用 fork 函数创建一个新的运行的子进程。
- 子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份副本。包括代码和数据段、堆、共享库以及用户栈。子进程还获得与父进程任何打开文件描述符相同的副本,这意味着子进程可以读写父进程中打开的任何文件,父进程和新创建的子进程之间最大的区别在于他们有不同的PID。
pid_t fork(void); // pid_t 在Linux中被定义为 int
- fork() 函数调用一次返回两次,一次返回到父进程,一个返回到新创建的子进程。子进程返回0,父进程返回子进程的PID,如果出错,则为-1。
- PID:当前进程ID
PPID:当前进程的父进程ID
LWP:线程ID
NLWP:线程组内线程的个数
示例:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
int i = 1;
pid_t pid;
pid = fork();
if (pid == 0) {
i = 2;
printf("child process. %d\n", i);
}
printf("father process. %d\n", i);
// 两次执行这条print,i值不同,因为已经有了不同的地址空间。
return 0;
}
运行结果:
father process. 1
child process. 2
father process. 2
僵尸进程
当一个进程由于某种原因终止时,内核并不是立即把它从系统中清除。相反,进程被保持在一种已终止的状态中,直到被它的父进程回收。一个终止了但还未被回收的进程称为僵尸进程。僵尸进程没有运行,但它仍然消耗系统的内存资源。
僵尸进程测试代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <unistd.h>
int main() {
pid_t pid;
pid = fork();
if (pid == 0) { // child process
printf("child process pid is running\n");
sleep(10);
return 0;
}
sleep(30);
printf("child process pid : %d\n", pid);
return 0;
}
这里子进程10s后先结束,因为父进程没有回收子进程,则子进程变成僵尸进程。
第一次 ps -al
时父进程子进程都在运行,第二次 ps -al
时子进程已经成为僵尸进程,但系统还未正式回收此进程,PID还在。
孤儿进程
如果一个父进程终止了,内核会安排init进程成为它的孤儿进程的养父。init进程的PID为1,是在系统启动时由内核创建的,它不会终止,是所有进程的祖先。如果父进程没有回收它的僵尸子进程就终止了,那么内核会安排init进程去回收他们。长时间运行的程序(shell,服务器等)应该回收他们的僵死子进程。
init进程 :
孤儿进程测试代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <unistd.h>
int main() {
pid_t pid;
pid = fork();
if (pid == 0) {
printf("child process pid is running\n");
sleep(30);
return 0;
}
sleep(10);
printf("child process pid : %d\n", pid);
return 0;
}
运行结果:
可以看到父进程PID为25524,子进程PID为25525,等10s后再次执行ps -al
命令,看到父进程已经消失,子进程的PPID更新为1363。
本来孤儿进程应该更新PPID为1,但这里Ubuntu 16.04
指定了 /sbin/upstart 收养孤儿进程,属于发行版的特殊功能吧。
守护进程
守护进程就是在后台运行,不与任何终端关联的进程,通常情况下守护进程在系统启动时就在运行,它们以root用户或者其他特殊用户(apache和postfix)运行,并能处理一些系统级的任务。习惯上守护进程的名字通常以d结尾(sshd),但这些不是必须的。
创建守护进程的大致步骤:
- 用
fork()
创建子进程,父进程退出,子进程成为孤儿进程被init接管,子进程变为后台进程。 - 调用
setsid()
让子进程成为新会话的组长,脱离父进程的会话期。 - 将当前目录改成跟目录(如果把当前目录作为守护进程的目录,当前目录不能被卸载他作为守护进程的工作目录)
- 关闭进程从创建它的父进程那里继承了打开的文件描述符。
- 将标准输入,标注输出,标准错误重定向到/dev/null