每一个进程是资源分配的基本单位。进程结构由以下几个部分组成:代码段、堆栈段、数据段。代码段是静态的二进制代码,多个程序可以共享。实际上在父进程创建子进程之后,父、子进程除了pid外,几乎所有的部分几乎一样,子进程创建时拷贝父进程PCB中大部分内容,而PCB的内容实际上是各种数据、代码的地址或索引表地址,所以复制了PCB中这些指针实际就等于获取了全部父进程可访问数据。所以简单来说,创建新进程需要复制整个PCB,之后操作系统将PCB添加到进程核心堆栈底部,这样就可以被操作系统感知和调度了。
父、子进程共享全部数据,但并不是说他们就是对同一块数据进行操作,子进程在读写数据时会通过写时复制机制将公共的数据重新拷贝一份,之后在拷贝出的数据上进行操作。如果子进程想要运行自己的代码段,还可以通过调用execv()函数重新加载新的代码段,之后就和父进程独立开了。我们在shell中执行程序就是通过shell进程先fork()一个子进程再通过execv()重新加载新的代码段的过程。
进程创建与结束
背景知识:
进程有两种创建方式,一种是操作系统创建的一种是父进程创建的。从计算机启动到终端执行程序的过程为:0号进程 -> 1号内核进程 -> 1号用户进程(init进程) -> getty进程 -> shell进程 -> 命令行执行进程。所以我们在命令行中通过 ./program执行可执行文件时,所有创建的进程都是shell进程的子进程,这也就是为什么shell一关闭,在shell中执行的进程都自动被关闭的原因。从shell进程到创建其他子进程需要通过以下接口。
相关接口:
创建进程:pid_t fork(void);
返回值:出错返回-1;父进程中返回pid > 0;子进程中pid == 0
结束进程:void exit(int status);
status是退出状态,保存在全局变量中S?,通常0表示正常退出。
获得PID:pid_t getpid(void);
返回调用者pid。
获得父进程PID:pid_t getppid(void);
返回父进程pid。
其他补充:
正常退出方式:exit()、_exit()、return(在main中)。
exit()和_exit()区别:exit()是对_exit()的封装,都会终止进程并做相关收尾工作,最主要的区别是_exit()函数关闭全部描述符和清理函数后不会刷新流,但是exit()会在调用_exit()函数前刷新数据流。
return和exit()区别:exit()是函数,但有参数,执行完之后控制权交给系统。return若是在调用函数中,执行完之后控制权交给调用进程,若是在main函数中,控制权交给系统。
异常退出方式:abort()、终止信号。
僵尸进程、孤儿进程
背景知识:
父进程在调用fork接口之后和子进程已经可以独立开,之后父进程和子进程就以未知的顺序向下执行(异步过程)。所以父进程和子进程都有可能先执行完。当父进程先结束,子进程此时就会变成孤儿进程,不过这种情况问题不大,孤儿进程会自动向上被init进程收养,init进程完成对状态收集工作。而且这种过继的方式也是守护进程能够实现的因素。如果子进程先结束,父进程并未调用wait或者waitpid获取进程状态信息,那么子进程描述符就会一直保存在系统中,这种进程称为僵尸进程。
相关接口:
回收进程(1):pid_t wait(int *status);
一旦调用wait(),就会立即阻塞自己,wait()自动分析某个子进程是否已经退出,如果找到僵尸进程就会负责收集和销毁,如果没有找到就一直阻塞在这里。
status:指向子进程结束状态值。
回收进程(2):pid_t waitpid(pid_t pid, int *status, int options);
返回值:返回pid:返回收集的子进程id。返回-1:出错。返回0:没有被手机的子进程。
pid:子进程识别码,控制等待哪些子进程。
pid < -1,等待进程组识别码为pid绝对值的任何进程。
pid = -1,等待任何子进程。
pid = 0,等待进程组识别码与目前进程相同的任何子进程。
pid > 0,等待任何子进程识别码为pid的子进程。
status:指向返回码的指针。
options:选项决定父进程调用waitpid后的状态。
options = WNOHANG,即使没有子进程退出也会立即返回。
options = WUNYRACED,子进程进入暂停马上返回,但结束状态不予理会。
守护进程
背景知识:
守护进程是脱离终端并在后台运行的进程,执行过程中信息不会显示在终端上并且也不会被终端发出的信号打断。
操作步骤:
创建子进程,父进程退出:fork() + if(pid > 0){exit(0);},使子进程称为孤儿进程被init进程收养。
在子进程中创建新会话:setsid()。
改变当前目录结构为根:chdir("http://www.likecs.com/")。
重设文件掩码:umask(0)。
关闭文件描述符:for(int i = 0; i < 65535; ++i){close(i);}。
Linux进程控制
进程地址空间(地址空间)