【linux】【操作系统】初始化程序之main.c源码阅读

news2024/9/21 22:12:20

在这里插入图片描述

/init/main.c是Linux操作系统启动过程的核心部分,它负责初始化硬件、设备、内存和系统服务,以及启动第一个用户进程,为后续的系统运行奠定基础。

详细解析

1. 内联函数定义
  • fork, pause, setup, sync: 这些函数被声明为内联,意味着它们在编译时会被直接嵌入到调用点,而不是通过常规函数调用的方式执行。这在内核上下文中特别重要,因为避免了不必要的栈操作和上下文切换,提高了效率。尤其是forkpause,由于它们在main函数中的关键作用,需要确保不会破坏栈的完整性。
1.1 fork内联函数分析

系统调用宏定义

#define __NR_setup	0	/* used only by init, to get system going */
#define __NR_exit	1
#define __NR_fork	2
#define _syscall0(type,name) \
type name(void) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
	: "=a" (__res) \
	: "0" (__NR_##name)); \
if (__res >= 0) \
	return (type) __res; \
errno = -__res; \
return -1; \
}

此宏定义 _syscall0 的功能是简化创建无参数系统调用函数的过程,其具体作用如下:

  1. 参数解析:
    • type: 指定系统调用函数的返回值类型。
    • name: 系统调用函数的名称。
  2. 函数体详解:
    • 定义一个长整型变量 __res 用于存储系统调用的结果。
    • 使用内联汇编 __asm__ 插入一条 int $0x80 指令,这是 Linux 中触发系统调用的标准方式。
    • 汇编指令使用寄存器传递参数和接收结果,其中 "=a" (__res) 表示将结果存储到 eax 寄存器(即 __res),而 "0" (__NR_##name) 表示从 eax 寄存器读取系统调用编号,这里 __NR_##name 是根据 name 动态生成的系统调用编号。
  3. 错误处理:
    • 如果系统调用成功,即 __res >= 0,则将 __res 转换为 type 类型并返回。
    • 如果系统调用失败,即 __res < 0,则将 -__res 的值赋给全局变量 errno,表示错误代码,并返回 -1
1.2 setup内联函数分析

这个宏提供了一种便捷方式来封装系统调用,避免了每次手动编写相似的汇编代码和错误处理逻辑。例如,可以这样使用:_syscall1(int, read, int, fd), 生成一个 read 函数,用于读取文件描述符 fd 的数据。

#define _syscall1(type,name,atype,a) \
type name(atype a) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
	: "=a" (__res) \
	: "0" (__NR_##name),"b" ((long)(a))); \
if (__res >= 0) \
	return (type) __res; \
errno = -__res; \
return -1; \
}
  1. 参数解析:

    • type: 指定系统调用返回值的数据类型。
    • name: 系统调用函数的名称。
    • atype: 系统调用单个参数的数据类型。
    • a: 系统调用的参数。
  2. 功能实现:

    • 定义了一个函数 name 接受参数 a 类型为 atype
    • 使用内嵌汇编指令 "int $0x80" 触发 Linux 系统调用机制。
    • __NR_##name 是系统调用编号,它根据 name 动态生成,如 __NR_open 对应 open 系统调用的编号。
    • 参数 a 被传递到寄存器 b 中,供系统调用使用。
    • 调用结果存储在 __res 变量中。
    • 如果 __res 大于等于 0,表示系统调用成功,将 __res 强制转换为 type 类型并返回。
    • 如果 __res 小于 0,表示系统调用失败,将 -__res 的值赋给全局变量 errno 表示错误码,函数返回 -1
2. 系统初始化与配置
  • 硬件与内存初始化:

    • EXT_MEM_KDRIVE_INFO 用于读取系统BIOS提供的扩展内存信息和磁盘驱动器信息。
    • memory_end, buffer_memory_end, main_memory_start: 这些变量用于确定系统可用的内存范围,以及分配给缓冲区的内存大小。根据系统总内存的不同,分配给缓冲区的内存也会相应调整。
  • 实时钟初始化 (time_init)

    • 通过访问CMOS寄存器读取系统时间,然后将其从二进制编码的十进制(BCD)格式转换为二进制格式,最后计算出系统启动的时间戳。
3. 设备与资源初始化
  • hd_init, floppy_init, blk_dev_init, chr_dev_init: 这些函数分别用于初始化硬盘、软盘、块设备和字符设备,确保系统可以访问和管理这些硬件资源。
3.1 hd_init分析

此函数主要负责硬盘设备的初始化工作,确保系统可以正确地与硬盘交互。

void hd_init(void)
{
	blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
	set_intr_gate(0x2E,&hd_interrupt);
	outb_p(inb_p(0x21)&0xfb,0x21);
	outb(inb_p(0xA1)&0xbf,0xA1);
}

blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;

这一行代码将硬盘设备的请求处理函数设置为 DEVICE_REQUEST。在Linux设备驱动模型中,blk_dev 是一个数组,存储了所有注册的块设备信息。MAJOR_NR 表示硬盘设备的主设备号,request_fn 是该设备结构体中的一个成员,用于指定当有I/O请求到达时应该调用哪个函数来处理这些请求。这里的 DEVICE_REQUEST 就是这个处理函数,它负责调度和执行具体的读写操作。

set_intr_gate(0x2E, &hd_interrupt);

#define _set_gate(gate_addr,type,dpl,addr) \
__asm__ ("movw %%dx,%%ax\n\t" \
	"movw %0,%%dx\n\t" \
	"movl %%eax,%1\n\t" \
	"movl %%edx,%2" \
	: \
	: "i" ((short) (0x8000+(dpl<<13)+(type<<8))), \
	"o" (*((char *) (gate_addr))), \
	"o" (*(4+(char *) (gate_addr))), \
	"d" ((char *) (addr)),"a" (0x00080000))

#define set_intr_gate(n,addr) \
	_set_gate(&idt[n],14,0,addr)

set_intr_gate 函数来设置一个中断服务程序(ISR)。0x2E 是硬盘设备的中断请求线(IRQ)编号,在这里被映射到处理器的中断向量表中。hd_interrupt 是实际的中断处理函数,当硬件检测到中断信号时,CPU会跳转到这个函数以处理中断事件,例如完成的数据传输或错误报告。

outb_p(inb_p(0x21) & 0xfb, 0x21);
这一行代码通过I/O端口 0x21 来访问并修改硬盘控制器的寄存器。inb_p 和 outb 分别是从I/O端口读取字节和向I/O端口写入字节的函数。首先,从端口 0x21 读取当前值,然后与 0xfb 进行按位与运算,目的是清除特定的比特位。这通常是为了禁用某个功能或设置控制器进入某种模式。

3.2 blk_dev_init分析

该函数功能是初始化块设备请求队列,NR_REQUEST32,将其设备标识符设为-1,表示未指定设备,并将其下一个请求指针设为NULL表示队列末尾。这样,函数将块设备的请求队列初始化为空队列状态。

void blk_dev_init(void)
{
	int i;

	for (i=0 ; i<NR_REQUEST ; i++) {
		request[i].dev = -1;
		request[i].next = NULL;
	}
}
4. main 函数流程
  • main 函数是程序的入口点,它首先禁用中断,进行必要的初始化,然后重新启用中断。
  • 执行一系列初始化函数:mem_inittrap_initblk_dev_initchr_dev_inittty_inittime_initsched_initbuffer_init,这些函数分别用于内存管理、陷阱处理、设备初始化、终端初始化、时间管理、调度初始化和缓冲区初始化。
  • 创建一个子进程(init),这是Linux系统中的第一个用户级进程,负责后续的系统初始化和进程管理。
5. init 函数细节
  • init 函数用于创建一个守护进程,关闭标准输入、输出和错误流,然后执行/bin/sh shell。
  • 如果fork失败或执行execve失败,会打印错误信息并退出。
  • 监控子进程的运行状态,当子进程终止时,会输出相关信息并同步文件系统。
6. main.c源码
/*
 *  linux/init/main.c
 *
 *  (C) 1991  Linus Torvalds
 */

#define __LIBRARY__
#include <unistd.h>
#include <time.h>

/*
 * we need this inline - forking from kernel space will result
 * in NO COPY ON WRITE (!!!), until an execve is executed. This
 * is no problem, but for the stack. This is handled by not letting
 * main() use the stack at all after fork(). Thus, no function
 * calls - which means inline code for fork too, as otherwise we
 * would use the stack upon exit from 'fork()'.
 *
 * Actually only pause and fork are needed inline, so that there
 * won't be any messing with the stack from main(), but we define
 * some others too.
 */
static inline _syscall0(int,fork)
static inline _syscall0(int,pause)
static inline _syscall1(int,setup,void *,BIOS)
static inline _syscall0(int,sync)

#include <linux/tty.h>
#include <linux/sched.h>
#include <linux/head.h>
#include <asm/system.h>
#include <asm/io.h>

#include <stddef.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>

#include <linux/fs.h>

static char printbuf[1024];

extern int vsprintf();
extern void init(void);
extern void blk_dev_init(void);
extern void chr_dev_init(void);
extern void hd_init(void);
extern void floppy_init(void);
extern void mem_init(long start, long end);
extern long rd_init(long mem_start, int length);
extern long kernel_mktime(struct tm * tm);
extern long startup_time;

/*
 * This is set up by the setup-routine at boot-time
 */
#define EXT_MEM_K (*(unsigned short *)0x90002)
#define DRIVE_INFO (*(struct drive_info *)0x90080)
#define ORIG_ROOT_DEV (*(unsigned short *)0x901FC)

/*
 * Yeah, yeah, it's ugly, but I cannot find how to do this correctly
 * and this seems to work. I anybody has more info on the real-time
 * clock I'd be interested. Most of this was trial and error, and some
 * bios-listing reading. Urghh.
 */

#define CMOS_READ(addr) ({ \
outb_p(0x80|addr,0x70); \
inb_p(0x71); \
})

#define BCD_TO_BIN(val) ((val)=((val)&15) + ((val)>>4)*10)

static void time_init(void)
{
	struct tm time;

	do {
		time.tm_sec = CMOS_READ(0);
		time.tm_min = CMOS_READ(2);
		time.tm_hour = CMOS_READ(4);
		time.tm_mday = CMOS_READ(7);
		time.tm_mon = CMOS_READ(8);
		time.tm_year = CMOS_READ(9);
	} while (time.tm_sec != CMOS_READ(0));
	BCD_TO_BIN(time.tm_sec);
	BCD_TO_BIN(time.tm_min);
	BCD_TO_BIN(time.tm_hour);
	BCD_TO_BIN(time.tm_mday);
	BCD_TO_BIN(time.tm_mon);
	BCD_TO_BIN(time.tm_year);
	time.tm_mon--;
	startup_time = kernel_mktime(&time);
}

static long memory_end = 0;
static long buffer_memory_end = 0;
static long main_memory_start = 0;

struct drive_info { char dummy[32]; } drive_info;

void main(void)		/* This really IS void, no error here. */
{			/* The startup routine assumes (well, ...) this */
/*
 * Interrupts are still disabled. Do necessary setups, then
 * enable them
 */
 	ROOT_DEV = ORIG_ROOT_DEV;
 	drive_info = DRIVE_INFO;
	memory_end = (1<<20) + (EXT_MEM_K<<10);
	memory_end &= 0xfffff000;
	if (memory_end > 16*1024*1024)
		memory_end = 16*1024*1024;
	if (memory_end > 12*1024*1024) 
		buffer_memory_end = 4*1024*1024;
	else if (memory_end > 6*1024*1024)
		buffer_memory_end = 2*1024*1024;
	else
		buffer_memory_end = 1*1024*1024;
	main_memory_start = buffer_memory_end;
#ifdef RAMDISK
	main_memory_start += rd_init(main_memory_start, RAMDISK*1024);
#endif
	mem_init(main_memory_start,memory_end);
	trap_init();
	blk_dev_init();
	chr_dev_init();
	tty_init();
	time_init();
	sched_init();
	buffer_init(buffer_memory_end);
	hd_init();
	floppy_init();
	sti();
	move_to_user_mode();
	if (!fork()) {		/* we count on this going ok */
		init();
	}
/*
 *   NOTE!!   For any other task 'pause()' would mean we have to get a
 * signal to awaken, but task0 is the sole exception (see 'schedule()')
 * as task 0 gets activated at every idle moment (when no other tasks
 * can run). For task0 'pause()' just means we go check if some other
 * task can run, and if not we return here.
 */
	for(;;) pause();
}

static int printf(const char *fmt, ...)
{
	va_list args;
	int i;

	va_start(args, fmt);
	write(1,printbuf,i=vsprintf(printbuf, fmt, args));
	va_end(args);
	return i;
}

static char * argv_rc[] = { "/bin/sh", NULL };
static char * envp_rc[] = { "HOME=/", NULL };

static char * argv[] = { "-/bin/sh",NULL };
static char * envp[] = { "HOME=/usr/root", NULL };

void init(void)
{
	int pid,i;

	setup((void *) &drive_info);
	(void) open("/dev/tty0",O_RDWR,0);
	(void) dup(0);
	(void) dup(0);
	printf("%d buffers = %d bytes buffer space\n\r",NR_BUFFERS,
		NR_BUFFERS*BLOCK_SIZE);
	printf("Free mem: %d bytes\n\r",memory_end-main_memory_start);
	if (!(pid=fork())) {
		close(0);
		if (open("/etc/rc",O_RDONLY,0))
			_exit(1);
		execve("/bin/sh",argv_rc,envp_rc);
		_exit(2);
	}
	if (pid>0)
		while (pid != wait(&i))
			/* nothing */;
	while (1) {
		if ((pid=fork())<0) {
			printf("Fork failed in init\r\n");
			continue;
		}
		if (!pid) {
			close(0);close(1);close(2);
			setsid();
			(void) open("/dev/tty0",O_RDWR,0);
			(void) dup(0);
			(void) dup(0);
			_exit(execve("/bin/sh",argv,envp));
		}
		while (1)
			if (pid == wait(&i))
				break;
		printf("\n\rchild %d died with code %04x\n\r",pid,i);
		sync();
	}
	_exit(0);	/* NOTE! _exit, not exit() */
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1967040.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

解决secureCRT乱码设置UTF-8显示中文

永久性配置UTF-8 在SecureCRT的安装文件夹中搜索“Default.ini” 找到D:“Filenames Always Use UTF8”00000000&#xff0c;将最后的0变成1&#xff0c;即&#xff1a;D:“Filenames Always Use UTF8”00000001 将S:”Output Transformer Name”Default修改为S:”Output Tran…

如何使用 PHP Simple HTML DOM Parser 轻松获取网页中的特定数据

背景介绍 网页数据的抓取已经成为数据分析、市场调研等领域的重要工具。无论是获取产品价格、用户评论还是其他公开数据&#xff0c;网页抓取技术都能提供极大的帮助。今天&#xff0c;我们将探讨如何使用 PHP Simple HTML DOM Parser 轻松获取网页中的特定数据。PHP Simple H…

SPSSAU | 最好最差权重BWM原理及案例实操分析

BWM&#xff08;best-worse-method&#xff0c;最好最差法&#xff09;是一种多准则决策方法&#xff0c;由Jafar Rezaei于2015年提出&#xff0c;其通常用于确定决策标准的权重。其原理是比如5个指标&#xff0c;如果以前AHP就需要5个指标两两的相对重要性数据。但是现在简化为…

CSS实现文本溢出处理

1.单行文本溢出 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-wid…

如何利用DFMEA优化产品设计?

在快节奏的产品开发周期中&#xff0c;DFMEA如同一双锐利的眼睛&#xff0c;帮助团队在设计初期就识别并预防潜在的问题。它不仅仅是一种分析方法&#xff0c;更是一种系统化的思维工具&#xff0c;通过深入分析每个设计元素可能发生的失效模式、原因、影响及严重程度&#xff…

Vue3父子组件传属性和方法调用Demo

Vue3父子组件传属性和方法调用Demo 说明目录父组件给子组件传值和方法父组件给子组件传值-使用defineProps接受父组件属性值父组件给子组件传值-使用defineModel接受父组件v-model值当子组件只需要接收父组件一个v-model值时,写法1如下:子组件接收单个v-model写法2如下:当子组件…

设计界的新宠:5款热门UI在线设计软件评测

随着用户界面设计行业的蓬勃发展&#xff0c;越来越多的设计师进入用户界面设计。选择一个方便的用户界面设计工具尤为重要&#xff01;除了传统的用户界面设计工具&#xff0c;在线用户界面设计工具也受到越来越多设计师的青睐。这种不受时间、地点、计算机配置限制的工作方法…

12. 计算机网络TCP四次挥手

1. 前言 上一章节分析了 TCP 建立连接的过程,既然有建立连接,对应的也有断开连接。数据传输完成之后,客户端和服务器端保持通信状态会占用资源开销,所以需要断开连接,TCP 协议中断开连接也被称为 TCP 四次挥手。 2.1 TCP 四次挥手 面试官提问: 说明一下 TCP 断开连接的…

Azure AD 配置角色,在Blazor中从Claims读取角色

首先是在 Microsoft Entra admin center 中配置你的应用程序角色 然后分配用户到你创建的角色 1. 首先从下图找到你要配置的程序 2. 然后找到分配用户到角色的地方 选择用户 再选择角色 这样就成功给用户分配了权限 接下来就可以在Blazor页面中读取了 using Microsoft.AspNe…

安居客全国小区(名称、价格、地区、地址)数据快速整理导出

安居客二手房小区全国(南京|重庆|青岛|天津|杭州|成都|沈阳|武汉|长沙|西安)实时数据&#xff0c;含小区名称、价格、地区、地址、商圈、标签、经纬度、物业类型、交易权属、竣工时间、产权年限、开发商、总户数、总建面积、绿化率、容积率、统一供暖 供水供电、停车位、停车费…

ruoyi若依框架中货道关联商品

<el-button link type"primary" click"handleGoods(scope.row)" v-hasPermi"[manage:vm:edit]">货道</el-button> <!-- 货道组件 --> <ChannelDialog :goodVisible"goodVisible" :goodData"goodData" …

职业本科综合布线实训室

一、职业本科综合布线实训室建设背景 在数字化时代的大潮中&#xff0c;网络技术作为推动社会进步的重要力量&#xff0c;其地位日益凸显。随着云计算、大数据、物联网、人工智能等技术的不断发展和融合&#xff0c;网络技术的边界和应用领域不断扩展&#xff0c;对于掌握现代…

无线领夹麦克风怎么选,直播唱歌只用领夹麦可以吗?

现如今视频自媒体行业还在蓬勃发展&#xff0c;麦克风对于自媒体行业可以说是必不可少的装备了&#xff0c;在互联网“内卷”的时代&#xff0c;各大视频博主、Up主、主播大多都会使用无线麦克风来辅助视频和直播内容输出。无线领夹麦克风作为视频行业中的麦克风新宠&#xff0…

script 加载的三种方式详解

首屏优化这个问题想必已经老生常谈了&#xff0c;在面试当中也是经常被提及到&#xff0c;例如&#xff0c;面试官&#xff1a;有没有做过首屏优化&#xff0c;首屏优化都有哪些方案&#xff1f;当然在首屏优化中并没有一套方案是一劳永逸的&#xff0c;要根据具体网站首页的需…

【vluhub】weblogin之xxe实体注入

XXE 漏洞 XXE漏洞&#xff0c;全称XML外部实体注入漏洞&#xff0c;是一种常见的针对解析XML输入的应用程序的安全漏洞。当应用程序在解析XML数据时&#xff0c;如果没有正确验证或限制实体引用&#xff0c;攻击者就可以通过构造恶意的XML输入&#xff0c;将外部实体引用进来&…

NLB快速实现IPv4服务的负载均衡

阿里云网络型负载均衡NLB&#xff08;Network Load Balancer&#xff09;支持TCP、UDP和TCPSSL协议&#xff0c;提供了强大的四层负载均衡能力。 为了实现IPv4服务的负载均衡&#xff0c;需要快速创建一个NLB实例&#xff0c;并将来自客户端的访问请求转发至后端服务器。 操作…

自闭症儿童能否摘帽?摘帽成功的秘诀揭秘

自闭症&#xff0c;这一曾经被视为不可逆转的障碍&#xff0c;如今在科学的进步与社会的关注下&#xff0c;正逐步展现出被“摘帽”的可能性。那么&#xff0c;自闭症儿童真的能完全摆脱这一标签&#xff0c;实现真正的“摘帽”吗&#xff1f;答案是肯定的&#xff0c;关键在于…

开发效率提升利器:5款支持C#语言的AI辅助编程工具

前言 在这个AI迅速发展的阶段&#xff0c;涌现出了一大批好用的AI辅助编程工具。AI辅助编程工具能够提高开发效率、改善代码质量、降低bug率&#xff0c;是现代软件开发过程中的重要助手。今天大姚给大家分享5款AI辅助编程工具&#xff08;并且都支持C#语言&#xff09;&#…

大屏自适应方案

1.npm下载 npm i autofit.js2.在项目中引入 import autofit from autofit.js3.init(&#xff09;初始化&#xff0c;注意&#xff1a;要在mounted&#xff08;&#xff09;里