IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> 一、Linux内核体系结构 -> 正文阅读

[系统运维]一、Linux内核体系结构


Linux源码版本:0.11

1. 内核模式与体系结构

1.1 操作系统的结构

tu

1.2 操作系统的工作方式

1.把操作系统从用户态切换到内核态(用户应用程序到内核的流程)
2.实现操作系统的系统调用(操作系统服务层)
3.应用操作系统提供的底层函数,进行功能实现
4.退出后从内核态切换到用户态

1.3 操作系统内核中各级模块的相互关联

1.Linux内核的整体模块:进程调度模块、内存管理模块、文件系统模块、进程间通信模块、驱动管理模块
2.每个模块间的关系

123

1.4 操作系统结构的独立性

分两层:管理层 实现层 优点:易于升级和维护
高版本的内核与低版本的内核之间的区别:
多的是内核驱动的种类 而内核驱动的管理模式并没有巨大的改变(零散型、分层型、设备树)
进程的调度算法发生了改变 而进程的管理方式并没有巨大的改变

2. 内核中断概括

2.1 目的

1.硬件的中断响应 --> 内核驱动中的中断
2.系统调用的函数响应(sys_call) --> 系统调用
3.自定义中断–>软件的软中断模式
4.信号中断(kill -signalnum)–>对了解信号的使用创建等有帮助
5.系统的异常和错误–>系统的异常获取 了解系统异常的作用

2.1 Linux的中断机制

2.1.1分类

硬中断:由电脑主机的8259A类似的硬件中断控制芯片发出的中断
ARM中断控制器发出的中断
软中断:异常 第一类:CPU自行保留的中断
第二类:系统调用异常

2.1.2 代码结构

中断前的处理过程,中断的回复过程 中断的执行过程
asm. s trap.c
system_ call.s fork.csignal.c exit.c sys.c

2.2 中断的工作流程

2.2.1 回忆

  1. 做CPU工作模式的转换
  2. 进行寄存器的拷贝与压栈
  3. 设置中断异常向量表
  4. 保存正常运行的函数返回值
  5. 跳转到对应的中断服务函数上运行
  6. 进行模式的复原以及寄存器的复原
  7. 跳转回正常工作的函数地址继续运行

2.2.2 Linux中中断的工作流程

  1. 将所有的寄存器值入栈
    (8086中)SS EFLAGS ESP CS EIP (ARM中) r0-r15
  2. 将异常码入栈(中断号)
  3. 将当前的函数返回值进行入栈(为了能后还原现场)
  4. 调用对应的中断服务函数
  5. 出栈函数返回值,返回所有入栈的寄存器值
  6. 返回所有入栈的寄存器值
    如图所示:
    中断前的处理过程,中断的回复过程 中断的执行过程
    asm. s trap.c
    system_ call.s fork.csignal.c exit.c sys.c
    图

2.3 中断的代码实现过程

分析源码中asm.s得到下图:
图

3. 内核进程管理

3.1 系统进程的运转方式

系统时间:(jiffies 系统嘀嗒)
CPU内部有一个RTC,会在上电的时候调用kernel_mktime函数算出从1970年1月1日0时到当前开机点所过的秒数。给kernel_mktime函数传来的时间结构体的赋值是由初始化时从RTC中读出来的参数转化为时间存入全局变量中,并且会为jiffies 所用
jiffies 是一个系统的时钟滴答,一个系统滴答是10ms
10ms一个系统滴答----->每隔10ms引发一个定时器中断(中断服务函数_timer_interrupt中,进行自增,然后调用do_timer)
do_timer函数

	if (cpl)//cpl表示当前被中断的进程是用户进程还是内核进程
		current->utime++;//utime 用户程序运行时间
	else
		current->stime++;//stime 内核程序运行时间
		if (next_timer) {// next_timer 是所有与jiffies变量有关的定时器的事件链表
		next_timer->jiffies--;
		while (next_timer && next_timer->jiffies <= 0) {
			void (*fn)(void);
			
			fn = next_timer->fn;
			next_timer->fn = NULL;
			next_timer = next_timer->next;
			(fn)();
		}
	}
	if (current_DOR & 0xf0)//取高四位
		do_floppy_timer();
	if ((--current->counter)>0) return;
	current->counter=0;//counter进程的时间片为0,task_struct[]是进程的向量表 
	//counter在哪里用? 进程的调度就是task_struct[]中检索,找时间片最大的进程对象来运行 直到时间片为0退出 之后再进行新一轮调用
	//counter在哪里被设置? 当task_struct[]所有进程的counter都为0,就进行新一轮的时间片分配
	if (!cpl) return;
	schedule();//这个就是进行时间片分配

在内核初始化的过程中,先sched init(),后调用move_to_user mode把内核状态从内核态切换到用户态,然后会手动创建0号进程,0号进程是所有进程的父进程

1.sched init();做了什么事情?
初始化了GDT描述符
图

2.在0号进程中:
1.打开标准输入输出错误的控制台句柄
2.创建1号进程,如果创建成功,则在一号进程中
首先打开了 “/etc/rc"文件
执行SHELL程序“/bin/sh”
3.0号进程不可能结束,他会在没有其他进程调用的时候调用,只会执行for(;😉 pause() ;

3.2 如何进行创建一个新的进程

进程创建:fork
1.在task链表中找一个进程空位存放当前的进程
2.创建一个task_struct
3.设置task_struct

进程的创建就是对0号进程或者当前进程的复制
0号进程复制就是结构体的复制 把task[0]对应的task_struct复制给新创建的task_struct
对于堆栈的拷贝 当进程做创建的时候要复制原有的堆栈
1.进程的创建是系统调用

.align 2
_sys_fork://fork的系统调用
	call _find_empty_process//调用这个函数
	testl %eax,%eax
	js 1f
	push %gs
	pushl %esi
	pushl %edi
	pushl %ebp
	pushl %eax
	call _copy_process//
	addl $20,%esp
1:	ret

2.进程的创建主体:copy_process函数

int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,
		long ebx,long ecx,long edx,
		long fs,long es,long ds,
		long eip,long cs,long eflags,long esp,long ss)
{
	struct task_struct *p;
	int i;
	struct file *f;
	//其实就是malloc分配内存
	p = (struct task_struct *) get_free_page();//在内存分配一个空白页,让指针指向它
	if (!p)
		return -EAGAIN;//如果分配失败就是返回错误
	task[nr] = p;//把这个指针放入进程的链表当中
	*p = *current;//把当前进程赋给p,也就是拷贝一份	/* NOTE! this doesn't copy the supervisor stack */
	//后面全是对这个结构体进行赋值相当于初始化赋值
	p->state = TASK_UNINTERRUPTIBLE;
	p->pid = last_pid;
	p->father = current->pid;
	p->counter = p->priority;
	p->signal = 0;
	p->alarm = 0;
	p->leader = 0;		/* process leadership doesn't inherit */
	p->utime = p->stime = 0;
	p->cutime = p->cstime = 0;
	p->start_time = jiffies;//当前的时间
	p->tss.back_link = 0;
	p->tss.esp0 = PAGE_SIZE + (long) p;
	p->tss.ss0 = 0x10;
	p->tss.eip = eip;
	p->tss.eflags = eflags;
	p->tss.eax = 0;//把寄存器的参数添加进来
	p->tss.ecx = ecx;
	p->tss.edx = edx;
	p->tss.ebx = ebx;
	p->tss.esp = esp;
	p->tss.ebp = ebp;
	p->tss.esi = esi;
	p->tss.edi = edi;
	p->tss.es = es & 0xffff;
	p->tss.cs = cs & 0xffff;
	p->tss.ss = ss & 0xffff;
	p->tss.ds = ds & 0xffff;
	p->tss.fs = fs & 0xffff;
	p->tss.gs = gs & 0xffff;
	p->tss.ldt = _LDT(nr);
	p->tss.trace_bitmap = 0x80000000;
	if (last_task_used_math == current)//如果使用了就设置协处理器
		__asm__("clts ; fnsave %0"::"m" (p->tss.i387));
	if (copy_mem(nr,p)) {//老进程向新进程代码段和数据段进行拷贝
		task[nr] = NULL;//如果失败了
		free_page((long) p);//就释放当前页
		return -EAGAIN;
	}
	for (i=0; i<NR_OPEN;i++)//
		if (f=p->filp[i])//父进程打开过文件
			f->f_count++;//就会打开文件的计数+1,说明会继承这个属性
	if (current->pwd)//跟上面一样
		current->pwd->i_count++;
	if (current->root)
		current->root->i_count++;
	if (current->executable)
		current->executable->i_count++;
	set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss));
	set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));
	p->state = TASK_RUNNING;//把状态设定为运行状态	/* do this last, just in case */
	return last_pid;//返回新创建进程的id号
}

3.3 进程状态

运行状态 可以被运行 只有在这个状态才能进行进程切换
可中断睡眠状态 可以被信号中断 变成running
不可中断睡眠状态 只能被wakeup唤醒 变成running
暂停状态 收到SIGSTOP SIGTSTP SIGTTIN这几个信号就会暂停
僵尸状态 进程停止运行 但是父进程还未将其清空

// sched.h宏定义运行状态
#define TASK_RUNNING		0   //可以被运行 只有在这个状态才能进行进程切换
#define TASK_INTERRUPTIBLE	1	//可以被信号中断 变成running
#define TASK_UNINTERRUPTIBLE	2 //只能被wakeup唤醒 变成running
#define TASK_ZOMBIE		3	//收到SIGSTOP SIGTSTP SIGTTIN这几个信号就会暂停
#define TASK_STOPPED		4 //进程停止运行 但是父进程还未将其清空

3.4 进程调度:优先级时间片轮转调度

void schedule(void) 进程调度函数
进程调度也是系统调用

reschedule:
	pushl $ret_from_sys_call
	jmp schedule
void schedule(void)
{
	int i,next,c;
	struct task_struct ** p;

/* check alarm, wake up any interruptible tasks that have got a signal */

	for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
		if (*p) {//alarm是进程定时器,用来设置警告,比如jiffies大于alarm时可能需要警告那么就用alarm来实现
			if ((*p)->alarm && (*p)->alarm < jiffies) {
					(*p)->signal |= (1<<(SIGALRM-1));
					(*p)->alarm = 0;
				}
				//~(_BLOCKABLE & (*p)->blocked  
				//&&(*p)->state==TASK_INTERRUPTIBLE
				//信号不为空,并且屏蔽某些不能引发进程就绪的信号
				//如果该进程为可中断睡眠状态 则如果该进程有非屏蔽信号出现就将该进程的状态设置为running
			if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) &&
			(*p)->state==TASK_INTERRUPTIBLE)
				(*p)->state=TASK_RUNNING;
		}

/* this is the scheduler proper: */
	// 以下思路,循环task列表 根据counter大小决定进程切换
	while (1) {
		c = -1;
		next = 0;
		i = NR_TASKS;
		p = &task[NR_TASKS];
		while (--i) {
			if (!*--p)
				continue;//进程为空就继续循环
			if ((*p)->state == TASK_RUNNING && (*p)->counter > c)//找出c最大的task
				c = (*p)->counter, next = i;
		}
		if (c) break;//如果c找到了,就终结循环,说明找到了 如果为0则所有进程的时间片都已经用完了,则需要进行时间片的重新分配
		//进行时间片的重新分配
		for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
			if (*p)//这里很关键,在低版本内核中,是进行优先级时间片轮转分配,这里搞清楚了优先级和时间片的关系
			//时间片分配原则:counter = counter/2 + priority
				(*p)->counter = ((*p)->counter >> 1) +
						(*p)->priority;
	}
	//切换到下一个进程 这个功能使用宏定义完成的
	switch_to(next);
}

switch_to(next) 上下文切换宏函数

// 进程切换是用汇编宏定义实现的
//进程切换只需要做两件事情:
//1. 将需要切换的进程赋值给当前进程指针(current,即task_struct指针)
//2. 将进程的上下文(通用寄存器TSS和当前堆栈中的信息)切换
#define switch_to(n) {\
struct {long a,b;} __tmp; \
__asm__("cmpl %%ecx,_current\n\t" \
	"je 1f\n\t" \
	"movw %%dx,%1\n\t" \
	"xchgl %%ecx,_current\n\t" \
	"ljmp %0\n\t" \
	"cmpl %%ecx,_last_task_used_math\n\t" \
	"jne 1f\n\t" \
	"clts\n" \
	"1:" \
	::"m" (*&__tmp.a),"m" (*&__tmp.b), \
	"d" (_TSS(n)),"c" ((long) task[n])); \
}

图

void sleep_on(struct task_struct **p)
当某个进程想访问CPU资源,但是CPU资源被占用访问不到,就会休眠
其实核心就是把state置为TASK_UNINTERRUPTIBLE不 可中断休眠

void sleep_on(struct task_struct **p)
{
	struct task_struct *tmp;

	if (!p)//如果传进来的是空的 就返回
		return;
	if (current == &(init_task.task))//当前进程是0号 
		panic("task[0] trying to sleep");//就打印并且返回
	tmp = *p;
	*p = current;//这两步相当于 给休眠链表添加了一个新node
	// 其实核心就是把state置为TASK_UNINTERRUPTIBLE
	current->state = TASK_UNINTERRUPTIBLE;
	schedule();
	if (tmp)
		tmp->state=0;
}

等待队列的实现:
图

void math_state_restore()
协处理器的恢复函数 协处理器其实就想象成是CPU,切换进程时,协处理器的一些寄存器栈堆等数据也要进行切换

3.4 进程的销毁的流程

1.1 exit是销毁函数-----一个系统调用----do exit
首先该函数会释放进程的代码段和数据段占用的内存
1.2关闭进程打开的所有文件,对当前的目录和i节点进行同步(文件操作)
1.3如果当前要销毁的进程有子进程,那么就让1号进程作为新的父进程(init进程)
1.4如果当前进程是一个会话头进程,则会终止会话中的所有进程
1.5改变当前进程的运行状态,变成TASK_ZONBIE僵死状态,并且向其父进程发送SIGCHLD信号。

2.1父进程在运行子进程的时候一般都会运行wait waitpid这两个函数(父进程等待某个子进程终止的函数)当父进程收到SIGCHLD信号时父进程会终止僵死状态的子进程
2.2首先父进程会把子进程的运行时间累加到自己的进程变量中
2.3把对应的子进程的进程描述结构体进行释放,置空任务数组中的空槽

void release(struct task struct * p)
完成清空了任务描述表中的对应进程表项,释放对应的内存页(代码段 数据段)

static inline int send_sig (long sig,struct task_struct * p,int priv)给制定的p进程发送对应的sig信号

static void kill_session(void)
终止会话,终止当前进程的会话给其发送SIGHUP

int sys_kill(int pid,int sig)
kill-不是杀死的意思,向对应的进程号或者进程组号发送任何信号
pid
{
pid>0给对应的pid发送sig
pid=0给当前进程的进程组发送sig
pid=-1给任何进程发送
pid<-1给进程组号为-pid的进程组发送信号)
}

//告诉父进程要死了
static void tell_father(int pid)

static void tell_father(int pid)
{
	int i;
	if (pid)
		for (i=0;i<NR_TASKS;i++) {
			if (!task[i])
				continue;
			if (task[i]->pid != pid)
				continue;
			task[i]->signal |= (1<<(SIGCHLD-1));//找到父亲发送SIGCHLD信号
			return;
		}
	printk("BAD BAD - no father found\n\r");
	release(current);//释放子进程
}

3.5 进程间通信

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2021-11-24 08:22:22  更:2021-11-24 08:23:38 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/16 0:56:56-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码