操作系统学习:Linux0.12初始化详细流程-进程退出与系统进入怠速
本文参考书籍
1.操作系统真相还原
2.Linux内核完全剖析:基于0.12内核
3.x86汇编语言 从实模式到保护模式
4.Linux内核设计的艺术
ps:基于x86硬件的pc系统
Linux0.12初始化续
此时系统已经加载了/etc/rc中的命令进行了执行,我们继续往下分析。
进程退出
execve("/bin/sh",argv_rc,envp_rc); // 系统调用执行命令_exit(2);
当execve执行完成后,此时就会调用_exit(2)这个函数执行,
volatile void _exit(int exit_code)
{__asm__("int $0x80"::"a" (__NR_exit),"b" (exit_code));
}
该函数直接调用了系统调用来处理,此时继续查找sys_exit函数,
int sys_exit(int error_code)
{do_exit((error_code&0xff)<<8); //调用do_exit函数error_code左移八位
}
继续查看do_exit函数;
volatile void do_exit(long code) // 程序退出函数
{struct task_struct *p;int i;free_page_tables(get_base(current->ldt[1]),get_limit(0x0f)); // 释放当前进程代码段和数据段所占内存页free_page_tables(get_base(current->ldt[2]),get_limit(0x17));for (i=0 ; i<NR_OPEN ; i++) // 遍历当前进程打开文件的描述符数组if (current->filp[i]) // 如果文件打开了则依次关闭打开文件sys_close(i);iput(current->pwd); // 放回工作目录inodecurrent->pwd = NULL; // 并置空iput(current->root); // 放回根目录inodecurrent->root = NULL; // 并置空iput(current->executable); // 放回执行文件的inodecurrent->executable = NULL; // 并置空iput(current->library); // 放回库文件current->library = NULL; // 并置空current->state = TASK_ZOMBIE; // 设置当前进程为僵死状态current->exit_code = code; // 设置进程退出状态码/* * Check to see if any process groups have become orphaned* as a result of our exiting, and if they have any stopped* jobs, send them a SIGUP and then a SIGCONT. (POSIX 3.2.2.2)** Case i: Our father is in a different pgrp than we are* and we were the only connection outside, so our pgrp* is about to become orphaned.*/if ((current->p_pptr->pgrp != current->pgrp) &&(current->p_pptr->session == current->session) &&is_orphaned_pgrp(current->pgrp) && // 如果父进程所在进程组与当前进程的不同,但都处于同一个会话has_stopped_jobs(current->pgrp)) { // 并且当前进程所在进程组将要变成孤儿进程并当前进程的进程组中含有处于停止状态的作业进程kill_pg(current->pgrp,SIGHUP,1); // 向当前进程组发送SIGHUP,SIGCONT信号kill_pg(current->pgrp,SIGCONT,1);}/* Let father know we died */current->p_pptr->signal |= (1<<(SIGCHLD-1)); // 通知父进程当前进程将终止/** This loop does two things:* * A. Make init inherit all the child processes* B. Check to see if any process groups have become orphaned* as a result of our exiting, and if they have any stopped* jons, send them a SIGUP and then a SIGCONT. (POSIX 3.2.2.2)*/if (p = current->p_cptr) { // 当前进程有子进程while (1) {p->p_pptr = task[1]; // 让进程1(init)成为父进程if (p->state == TASK_ZOMBIE) // 如果子进程已经是僵死状态task[1]->signal |= (1<<(SIGCHLD-1)); // 则向父进程发送子进程已终止信号/** process group orphan check* Case ii: Our child is in a different pgrp * than we are, and it was the only connection* outside, so the child pgrp is now orphaned.*/if ((p->pgrp != current->pgrp) &&(p->session == current->session) &&is_orphaned_pgrp(p->pgrp) &&has_stopped_jobs(p->pgrp)) { // 孤儿进程组检查,子进程在不同的进程组中,而本进程是唯一与外界的连接,子进程所在进程将变成孤儿进程组kill_pg(p->pgrp,SIGHUP,1);kill_pg(p->pgrp,SIGCONT,1);}if (p->p_osptr) { // 如果该子进程有兄弟进程,则继续循环处理兄弟进程p = p->p_osptr;continue;}/** This is it; link everything into init's children * and leave */p->p_osptr = task[1]->p_cptr; // 将p的兄弟进程加入init子进程链表中task[1]->p_cptr->p_ysptr = p; // 设置init最年轻的子进程为ptask[1]->p_cptr = current->p_cptr; current->p_cptr = 0; // 置空break;}}if (current->leader) { // 如果当前进程是会话进程struct task_struct **p;struct tty_struct *tty;if (current->tty >= 0) { // 若有控制终端tty = TTY_TABLE(current->tty); // 向该控制终端的进程组发送挂断信号if (tty->pgrp>0)kill_pg(tty->pgrp, SIGHUP, 1);tty->pgrp = 0; // 释放该会话tty->session = 0;}for (p = &LAST_TASK ; p > &FIRST_TASK ; --p) // 需要任务组if ((*p)->session == current->session) // 将属于当前进程会话中进程的中断置空(*p)->tty = -1;}if (last_task_used_math == current) // 检查上次是否使用了协处理器last_task_used_math = NULL; // 如果使用则置空
#ifdef DEBUG_PROC_TREE // 如果调试模式则打印进程树audit_ptree();
#endifschedule(); // 重新调度
}
该函数主要是释放当前进程代码段和数据段所占的内存页,关闭对应打开文件,放回相应当前进程的根目录、工作目录等,并设置当前进程为僵死状态、设置进程退出码,最后设置相关子进程相关操作,最后执行完成后调用调度函数重新调度。其中相关使用函数备注如下;
int is_orphaned_pgrp(int pgrp) // 判断是否是孤儿进程
{struct task_struct **p;for (p = &LAST_TASK ; p > &FIRST_TASK ; --p) { // 扫描任务数组if (!(*p) || // 如果任务为空((*p)->pgrp != pgrp) || // 进程的组号与指定的不同((*p)->state == TASK_ZOMBIE) || // 进程处于僵死状态((*p)->p_pptr->pid == 1)) // 或者进程的父进程是initcontinue; // 继续循环if (((*p)->p_pptr->pgrp != pgrp) && // 如果该父进程的父进程的组号不等于指定的组号((*p)->p_pptr->session == (*p)->session)) // 并且父进程的会话号等于进程的会话号return 0; // 则返回不是}return(1); /* (sighing) "Often!" */ // 返回是
}static int has_stopped_jobs(int pgrp)
{struct task_struct ** p;for (p = &LAST_TASK ; p > &FIRST_TASK ; --p) { // 循环查找查找当前任务是否包含已经处于停止的任务if ((*p)->pgrp != pgrp)continue;if ((*p)->state == TASK_STOPPED)return(1);}return(0);
}
其中有关kill_pg函数的分析留待后文分析。
至此,由init生成的执行/etc/rc进程执行完成后就调用_exit函数退出。此时,查看init进程的执行。
init进程等待子进程退出
此时父进程一直在执行;
if (pid>0)while (pid != wait(&i)) // 父进程等待子进程执行完成/* nothing */;
此时我们查看wait函数,
pid_t wait(int * wait_stat)
{return waitpid(-1,wait_stat,0); // 系统调用waitpid函数
}
调用waitpid就会进行系统调用,sys_waitpid函数
int sys_waitpid(pid_t pid,unsigned long * stat_addr, int options) // 挂起当前进程,直到pid指定的子进程退出
{int flag;struct task_struct *p;unsigned long oldblocked;verify_area(stat_addr,4); // 验证将要存放状态信息的位置处内存空间是否足够
repeat:flag=0; // 复位标志for (p = current->p_cptr ; p ; p = p->p_osptr) { // 当前进程中最年轻子进程开始扫描子进程兄弟链表if (pid>0) { // 如果pid大于0if (p->pid != pid) // 如果当前进程Pid不等于传入pid则继续循环continue;} else if (!pid) { // 如果传入pid为0表示正在等待进程组号等于当前进程组号的任何子进程if (p->pgrp != current->pgrp) // 如果此时找到的p的进程组号与当前进程的组号不等则跳过continue;} else if (pid != -1) { // 如果传入不等于-1,表示正在等待进程组号等于pid绝对值的任何子进程if (p->pgrp != -pid) // 如果此时找到的进程p的组号不等于pid的绝对值则跳过continue;}switch (p->state) { // 此时传入pid值为-1,此时找到进程的状态case TASK_STOPPED: // 如果是停止状态if (!(options & WUNTRACED) || !p->exit_code) // 如果程序无需立即返回或者找到进程没有退出吗continue; // 继续查找put_fs_long((p->exit_code << 8) | 0x7f,stat_addr); // 把退出码移入高字节p->exit_code = 0; // 重置当前进程的退出码return p->pid; // 返回当前进程的pidcase TASK_ZOMBIE: // 如果当前进程是僵死状态current->cutime += p->utime; // 将子进程的用户态和内核态运行时间累加到父进程中current->cstime += p->stime;flag = p->pid; // 将找到进程的pid赋值给标志位flagput_fs_long(p->exit_code, stat_addr); release(p); // 释放该进程
#ifdef DEBUG_PROC_TREE // 如果定义了调试audit_ptree(); // 显示进程树
#endifreturn flag; // 返回piddefault:flag=1; // 默认flag为1继续循环continue;}}if (flag) { // 如果flag为1则表示有符合等待要求的子进程并没有处于退出立刻或僵死状态if (options & WNOHANG) // 如果已设置则立刻返回return 0; current->state=TASK_INTERRUPTIBLE; // 设置当前进程为可中断等待状态oldblocked = current->blocked; // 保留当前进程信号阻塞位图current->blocked &= ~(1<<(SIGCHLD-1)); // 重置进程信号位图schedule(); // 重新调度current->blocked = oldblocked; // 重置阻塞位图if (current->signal & ~(current->blocked | (1<<(SIGCHLD-1)))) // 如果本进程收到处SIGCHLD以外的其它未屏蔽信号则返回退出码return -ERESTARTSYS;else // 否则继续repeatgoto repeat;}return -ECHILD; // 若flag为0表示没有找到符合要求的子进程,返回出错码
}
该函数有三个传入参数,如果传入pid>0,表示等待进程号为pid的子进程,如果传入pid=0,表示等待进程组号等于当前进程组号的任何子进程,当pid<-1,表示等待进程组号等于pid绝对值的任何子进程,如果pid=-1,表示等待任何子进程,如果传入options=WUNTRACED,表示如果子进程是停止的,马上返回,如果options=WNOHANG,如果没有子进程退出或终止就马上返回。主要实现了挂起当前进程,直到pid指定的子进程退出或者收到要求终止该进程的信号,或者是需要调用一个信号句柄,如果pid所指的子进程已退出则立刻返回,子进程使用的资源将释放。
此时当使用waitpid(-1,wait_stat,0)函数时,则需要等待子进程执行完成则返回,此处又使用了循环while (pid != wait(&i)),所以此处会一直等待子进程执行完成后,才会退出循环。当子进程执行完成后退出循环后,此时就需要加载shell了
init进程子进程执行加载shell终端
if (pid>0)while (pid != wait(&i)) // 父进程等待子进程执行完成/* nothing */;while (1) {if ((pid=fork())<0) {printf("Fork failed in init\r\n");continue; // 如果fork出错则继续fork}if (!pid) { // 新的子进程close(0);close(1);close(2); setsid(); // 创建一组会话(void) open("/dev/tty1",O_RDWR,0); // 以读写的方式打开终端(void) dup(0); (void) dup(0);_exit(execve("/bin/sh",argv,envp)); // 执行shell程序}while (1)if (pid == wait(&i)) // 如果子进程退出则继续循环break; // 停止循环printf("\n\rchild %d died with code %04x\n\r",pid,i);sync(); // 同步操作,刷新缓冲区}
此时就会进入该循环,然后调用fork函数,让子进程执行加载shell终端的任务。
其中close函数系统调用的是sys_close,setsid()对应sys_setsid的系统调用。
int sys_close(unsigned int fd)
{ struct file * filp;if (fd >= NR_OPEN) // 如果大于设置进程打开文件数的个数则返回出错码return -EINVAL;current->close_on_exec &= ~(1<<fd); // 复位进程的执行时关闭文件句柄位图if (!(filp = current->filp[fd])) // 如果当前描述符对应的文件结构为空则返回出错码return -EINVAL;current->filp[fd] = NULL; // 设置当前文件结构为空if (filp->f_count == 0) // 如果文件引用计数已经为0则出错panic("Close: file count is 0");if (--filp->f_count) // 否则对应文件结构的引用计数减1,如果此时引用计数为0 return (0);iput(filp->f_inode); // 此时放入该节点return (0);
}
...int sys_setsid(void)
{if (current->leader && !suser()) // 如果当前进程已是会话首领并不是超级用户则返回错误码return -EPERM;current->leader = 1; // 设置当前进程为新会话首领current->session = current->pgrp = current->pid; // 设置当前进程会话号和组号都为进程号current->tty = -1; // 设置当前进程没有控制终端return current->pgrp; // 返回进程pid号
}
至此,Linux0.12的初始化过程已经完成。
操作系统学习:Linux0.12初始化详细流程-进程退出与系统进入怠速相关推荐
- 操作系统学习:Linux0.12初始化详细流程-进程1加载虚拟盘和根文件系统安装
本文参考书籍 1.操作系统真相还原 2.Linux内核完全剖析:基于0.12内核 3.x86汇编语言 从实模式到保护模式 4.Linux内核设计的艺术 ps:基于x86硬件的pc系统 Linux0.1 ...
- 操作系统学习:Linux0.12初始化详细流程-进程1调度与读取硬盘数据
本文参考书籍 1.操作系统真相还原 2.Linux内核完全剖析:基于0.12内核 3.x86汇编语言 从实模式到保护模式 4.Linux内核设计的艺术 ps:基于x86硬件的pc系统 Linux0.1 ...
- 操作系统学习:Linux0.12初始化详细流程-首个子进程
本文参考书籍 1.操作系统真相还原 2.Linux内核完全剖析:基于0.12内核 3.x86汇编语言 从实模式到保护模式 4.Linux内核设计的艺术 ps:基于x86硬件的pc系统 Linux0.1 ...
- 操作系统学习:Linux0.12初始化详细流程-打开文件与加载可执行程序
本文参考书籍 1.操作系统真相还原 2.Linux内核完全剖析:基于0.12内核 3.x86汇编语言 从实模式到保护模式 4.Linux内核设计的艺术 ps:基于x86硬件的pc系统 Linux0.1 ...
- 操作系统学习:系统调用与Linux0.12初始化详细流程
本文参考书籍 1.操作系统真相还原 2.Linux内核完全剖析:基于0.12内核 3.x86汇编语言 从实模式到保护模式 4.Linux内核设计的艺术 ps:基于x86硬件的pc系统 系统调用 系统调 ...
- 【操作系统学习笔记】—— 【二】进程、线程、死锁
本文参考: JavaGuide 王道考研-操作系统 CS-Notes 文章目录 一.进程的概念.组成.特征 1. 概念 2. 进程的组成 PCB 程序段 数据段 3. 进程的特征 二.进程的状态 三. ...
- 操作系统学习:进程、线程与Linux0.12初始化过程概述
本文参考书籍 1.操作系统真相还原 2.Linux内核完全剖析:基于0.12内核 3.x86汇编语言 从实模式到保护模式 ps:基于x86硬件的pc系统 进程 进程是一种控制流集合,集合中至少包含一条 ...
- 操作系统学习:Linux0.12文件异步IO
本文参考书籍 1.操作系统真相还原 2.Linux内核完全剖析:基于0.12内核 3.x86汇编语言 从实模式到保护模式 4.Linux内核设计的艺术 ps:基于x86硬件的pc系统 Linux0.1 ...
- 鸿蒙操作系统详细流程,鸿蒙系统的启动流程
鸿蒙系统的启动流程 Liangkz 2021.04.11 Ver1.0 目录 1. 第一阶段:U-Boot启动 2. 第二阶段:汇编代码引导LiteOS-a内核 3. 第三阶段:内核LiteOS-a的 ...
最新文章
- python3版本代码大全_python3中的
- Markdown简单语法
- 数据分析之全国热门景点分析
- 【PAT乙级】1087 有多少不同的值 (20 分)
- mac下virtualbox安装win7系统
- PHP5.2至5.6的新增功能详解
- c语言如何用双重循环去重,c语言中一个一维数组怎样去重?
- [密码学基础][每个信息安全博士生应该知道的52件事][Bristol Cryptography][第3篇]影响计算能力和存储能力的因素
- 校园招聘 - 比较容易的面试题
- vue图片加载失败使用默认图片,el-image支持懒加载,自定义占位、加载失败等
- 更新无限无线连接更新服务器,02-H3C WBC560多业务无线控制器软件升级操作指导...
- TCP_IP Sockets编程C语言实现第2版 源码下载
- 使用 redis 减少 秒杀库存 超卖思路 (转)
- 远程迅雷linux,Ubuntu 14.04安装迅雷Xware过程笔记
- QT Libvlc音视频环境配置及编译错误解决
- MYSQL 名人博客
- android listview 上拉图片闪烁,android listview使用glide异步加载图片错位,闪烁问题...
- python-turtle(海龟绘图)圣诞树
- python定义一个有长度的列表
- 手写 call、apply 及 bind 函数
热门文章
- 超硬核全套Java视频教程(学习路线+免费视频+配套资料)
- 被Python「苦虐」的日子太惨了!
- 后深度学习时代的一大研究热点?论因果关系及其构建思路
- Spark入门系列(二)| 1小时学会RDD编程
- 碾压Bert?“屠榜”的XLnet对NLP任务意味着什么
- 谷歌大神Jeff Dean点赞网红博士论文:改进分布式共识机制 | 技术头条
- 前端、云与人工智能的碰撞 | GDG广州
- Google AI的焦虑:拆分搜索和人工智能部门,Jeff Dean任AI业务负责人
- 送书 | 深入浅出,一起学习贝叶斯!
- 有个程序员老公有多爽???