1、Linux中init进程就是init程序。【详细见另一篇博客init进程】
2、进程ID:内核分配pid的值,不会重用以前分配过的值。
3、每个进程都属于一个进程组 ,区别于用户、组的概念。【详见 用户、组】。从用户角度看进程组更像是一个任务
4、exec函数簇:
#include <unistd.h> int execl( const char *pathname, const char *arg0, ... /* (char *)0 */ ); int execv( const char *pathname, char *const argv[] ); int execle( const char *pathname, const char *arg0, ... /* (char *)0, char *const envp[] */ ); int execve( const char *pathname, char *const argv[], char *const envp[] ); int execlp( const char *filename, const char *arg0, ... /* (char *)0 */ ); int execvp( const char *filename, char *const argv[] );6个函数返回值:若出错则返回-1,若成功则函数不返回
只有execve是内核的系统调用。另外5个只是库函数,它们最终都要调用该系统调用
exec执行前后
保留的属性有: 进程ID和父进程ID。实际用户ID和实际组ID。附加组ID。进程组ID。会话ID。控制终端。闹钟尚余留的时间。当前工作目录。根目录。文件模式创建屏蔽字。文件锁。进程信号屏蔽。未处理信号。资源限制。tms_utime、tms_stime、tms_cutime以及tms_cstime值。
改变的属性有:进程的地址空间,进程映像,挂起的信号,内存的锁定,多数关于线程的属性会复位,与进程内存的相关的任何数据都会丢失,包括映射的文件,关于进程的统计信息会复位。
对于打开的文件:与文件描述符的执行时关闭标志位有关系。一般会在调用exec函数前关闭打开的文件。可以调用fcntl函数完成。
5、fork()系统调用
fork()执行后,子进程和父进程区别:pid ppid 进程中资源的统计信息会清零,任何挂起的信号会清除,文件锁不会被继承。
写时复制:以页为单位,逐页复制。只要进程不修改它全部的地址空间,那么就不必复制整个地址空间。实现:当内核修改共享的数据时候,产生一个缺页中断,内核处理缺页中断处理函数,执行一次透明复制,并清除cow属性,表示不再共享。
fork()函数:调用一次,返回两次。
返回值:-1:错误; 0子进程的返回; 正数就代表创建的子进程的pid号。
fork()系统调用是Unix下以自身进程创建子进程的系统调用,一次调用,两次返回,如果返回是0,则是子进程,如果返回值>0,则是父进程(返回值是子进程的pid),这是众为周知的。 还有一个很重要的东西是,在fork()的调用处,整个父进程空间会原模原样【fork位置处的状态】地复制到子进程中,包括指令,变量值,程序调用栈,环境变量,缓冲区,等等。【注释,fork后有独立的栈】 int main(void) { int i; for(i=0; i<2; i++){ fork(); printf("-"); } wait(NULL); wait(NULL); return 0; }//一共输出8个-int main(void) { int i; for(i=0; i<2; i++){ fork(); //注意:下面的printf有“\n” printf("ppid=%d, pid=%d, i=%d \n", getppid(), getpid(), i); } sleep(10); //让进程停留十秒,这样我们可以用pstree查看一下进程树 return 0; }
6、vfork()函数:只完成一件事:复制内核数据结构。子进程不能修改地址空间中的内存。
fork 是 创建一个子进程,并把父进程的内存数据copy到子进程中。子进程有独立的栈,并且初试值是从父进程复制来的。vfork是 创建一个子进程,并和父进程的内存数据share一起用。没有独立的栈,完全公用所有的一切。vfork()不能调用return结束进程,这样会毁掉父进程的栈空间 。应该用exit()函数退出子进程。
本来vfork是为了exec()函数而生,因为传统的fork()太重(写时复制之前的fork)。vfork几乎避免了复制任何数据。
7、exit()函数:exit(0):正常退出; exit(1)异常退出
_exit()函数:定义在unistd.h中;直接使进程停止运行,清除其使用的内存空间,并销毁其在内核中的各种数据结构;exit() 函数则在这些基础上作了一些包装,在执行退出之前加了若干道工序。
exit()函数在调用exit系统调用之前要检查文件的打开情况,把文件缓冲区中的内容写回文件,就是"清理I/O缓冲"。
8、子进程的终止
通常父进程需要知道子进程是否结束。用信号通知父进程是可以的。但有时候父进程需要知道更多的信息。
僵死进程:当子进程在父进程之前结束,子进程会保留最小的概要信息(内核数据结构),等待父进程来查询,只要父进程获取了子进程的信息,子进程就会消失,否则一直保持僵死状态。
父进程先结束:内核遍历该进程的子进程,并将其子进程的父进程设置为init进程
9、wait()
函数原型:int wait(int *status)
函数功能是:父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。