几个基本概念

进程

进程的经典定义就是一个执行中的程序的实例,系统中的每一个程序都是运行在某个进程的上下文中的,上下文由程序正确运行所需要的状态组成的,这个状态包括存放载存储器中的程序的代码和数据,它的栈,通用目的寄存器的内容,程序计数器,环境变量以及打开文件描述符的集合。


关键抽象

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()时设置的字段。