为了能够使子进程和父进程执行不同的代码(子进程从当前父进程执行处开始执行),在fork()之后应该根据fork()返回值使用分支结构来组成程序代码。
int main(void)
{
pid_t pid;
pid = fork();
if(pid < 0){
... //打印fork()失败信息
}else if(pid == 0){
... //子进程代码
}else{
... //父进程代码
}
return 0;
}
另一种使子进程执行不同代码的方法是execv()系统调用,子进程调用execv()后,系统会立即为子进程分配私有程序内存空间,并把函数参数path所指定的可执行文件加载该空间中,从此子进程也成为一个真正的进程。
与一般函数不同,exec族函数执行成功后一般不会返回调用点,因为它运行了一个新的程序,进程的代码段、数据段、和堆栈都被新的数据取代。只有调用失败,才会返回一个-1,从原程序的调用点接着执行。
Linux采用了写时拷贝,即fork()不复制父进程的代码区,而是使用指针共享一份拷贝。这样做的好处就是,如果子进程调用execv()拷贝了其它代码段,就能避免父进程代码段无效的复制;如果依然使用父进程代码,也可以在父或子进程任一方写入时,再为子进程拷贝。
int execv(const char* path, char* const argv[]);
int main(void)
{
pid_t pid;
if(!(pid=fork())){
execv("./hello.o",NULL);//可执行文件./hello可以编写一个.c程序再进行编译获得
}else {
printf("my pif is %d\n", getpid());
}
return 0;
}
1号和2号进程的创建是start_kernel初始化到最后由test_init通过kernel_thread创建了两个内核线程:一个是kernel_init,把用户态的进程init启动起来,是所有用户进程的祖先;另一个是kthreadd内核线程,kthreadd是所有内核线程的祖先,负责管理所有内核线程。
1.1.3 进程的调度Linux的进程调度使用了剥夺方式,剥夺原则有优先权原则、短进程、优先原则、时间片原则。进程的调度算法有FIFO(非剥夺)、最短CPU运行期优先、优先权调度、时间片轮转。
调度时机
1、进程状态发生变化时
2、当前进程时间片用完时
3、进程从系统返回到用户态时
4、中断处理后,进程返回到用户态时
进程切换
1、切换页全局目录以安装一个新的地址空间
2、切换内核态堆栈和硬件上下文
1.1.4 进程的终止
僵尸进程
当子进程终止,OS释放掉其资源。但是由于父进程未调用wait(),所以它位于今称表中的条目还存在,这样的进程被称为僵尸进程。
孤儿进程
如果父进程没有调用wait()就终止,以至于子进程称为孤儿进程处于僵尸态。Linux对此情况的处理是,任选一个进程作为其父进程,如init进程。init进程定期调用wait(),以便定期释放挂在其下的僵尸进程。
1.2 内存管理内存管理用于确保所有进程能够安全地共享机器主内存区,同时,内存管理模块还支持虚拟内存管理方式,使得 Linux 支持进程使用比实际内存空间更多的内存容量。并可以利用文件系统把暂时不用的内存数据块会被交换到外部存储设备上去,当需要时再交换回来。Linux采用虚拟地址,在 32 位 Linux 系统上每个进程有4GB 的进程地址空间,在用户态下,只能访问 0x00000000~0xbfffffff 的地址空间,而内核态下可以访问全部空间。linux的地址分为逻辑地址、线性地址和物理地址。逻辑地址和线性地址在 32 位和 64 位上目前都是虚拟地址,需要依次经过分段映射和分页映射最后才转换成物理地址。这个映射计算地址的过程一般由 CPU 内部的 MMU(内存管理单元)负责把虚拟地址转换为物理地址。
1.3 设备管理