几个基本概念
进程
进程的经典定义就是一个执行中的程序的实例,系统中的每一个程序都是运行在某个进程的上下文中的,上下文由程序正确运行所需要的状态组成的,这个状态包括存放载存储器中的程序的代码和数据,它的栈,通用目的寄存器的内容,程序计数器,环境变量以及打开文件描述符的集合。
关键抽象
1.一个独立的逻辑控制流,他提供一个假象,好像我们的程序独占使用使用处理器。
2.一个私有的空间地址,它提供一个假象,好像我们的程序独占地使用存储器系统。
调度
在程序执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程。
上下文切换
1)保存当前进程的上下文;2)回复某个先前被抢占的进程被保存的上下文;3)将控制传递给这个新回复的进程。
fork()的封装
fork函数用于在进程中开启一个子进程。fork函数被调用一次会有两次返回:一个在调用父进程,返回子进程的PID,一个在子进程,返回0.
当Unix系统函数遇到错误时,他们典型地会返回-1,并设置全局整数变量erron来表示出了什么错,常用检错方法如下:
#include <stdio.h> #include <errno.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> int main() { pid_t pid; if(pid=fork()<0){ printf("fork error:%s\n",strerror(errno)); exit(0); } printf("Hello!\n"); return 1; }
下面对fork进行封装。
#include <stdio.h> #include <errno.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> pid_t Fork(); void unix_error(const char *msg); int main() { int x=1; pid_t pid; pid=Fork(); if(pid==0){ printf("child:x=%d\n",++x); printf("ppid=%d, pid=%d, i=%d \n", getppid(), getpid(), x); exit(0); } else{ printf("father:x=%d\n",--x); printf("ppid=%d, pid=%d, i=%d \n", getppid(), getpid(), x); } return 1; } pid_t Fork() { pid_t pid; if(pid=fork()<0) unix_error("Fork error"); return pid; } void unix_error(const char *msg) { printf("%s:%s\n",msg,strerror(errno)); exit(0); }
这样,对fork的调用只用一行代码就可以搞定。编译运行,得到的结果
并不是我们预想的结果,发现都是打印的child,但实际上,pid显示 为两个不同的进程,所以封装出现了问题。
仔细查看,发现这一行
if(pid=fork()<0) unix_error("Fork error");
有问题,根据c语言的符号运算顺序,赋值运算是放在最后的,所以应该改在pid=fork() 上添加一组括号。如下:
if((pid=fork())<0) unix_error("Fork error");
编译运行:
得到正确结果。
例子解析
在语句fpid=fork()之前,只有一个进程在执行这段代码,但在这条语句之后,就变成两个进程在执行了,这两个进程的几乎完全相同,将要执行的下一条语句都是if(fpid<0)……
为什么两个进程的fpid不同呢,这与fork函数的特性有关。fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
1)在父进程中,fork返回新创建子进程的进程ID;
2)在子进程中,fork返回0;
3)如果出现错误,fork返回一个负值;
在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。
引用一位网友的话来解释fpid的值为什么在父子进程中不同。“其实就相当于链表,进程形成了链表,父进程的fpid(p 意味point)指向子进程的进程id, 因为子进程没有子进程,所以其fpid为0.
fork出错可能有两种原因:
1)当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN。
2)系统内存不足,这时errno的值被设置为ENOMEM。
创建新进程成功后,系统中出现两个基本完全相同的进程,这两个进程执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略。
每个进程都有一个独特(互不相同)的进程标识符(process ID),可以通过getpid()函数获得,还有一个记录父进程pid的变量,可以通过getppid()函数获得变量的值。
还有下面值得注意的地方:
并发执行:父进程和子进程是并发执行的独立进程。内核能以任何形式替换执行他们的逻辑控制流中的指令。
相同的但是独立的地址空间:每个进程有相同的用户栈,相同的本地变量值,相同的堆,相同的全局变量值以及相同的代码,同时,他们会有自己私有的地址空间,父进程和子进程对x做的任何改变都是独立的,不会反映在另一个进程的存储器中。
关于进程描述符(task_struct)
在linux 中每一个进程都由task_struct 数据结构来定义. task_struct就是我们通常所说的PCB.她是对进程控制的唯一手段也是最有效的手段. 当我们调用fork() 时, 系统会为我们产生一个task_struct结构。然后从父进程,那里继承一些数据, 并把新的进程插入到进程树中, 以待进行进程管理。
结构体中一些关键的字段有:
state:记录进程状态;
pid:进程标识符;
binfmt:可执行文件的格式;
exit_signal :终止信号;
pdeath_singnal:父进程消亡的时候发出的信号;
comm:调用可执行程序创建进程时的程序名;
ptrace:调用ptrace()时设置的字段。