1.操作系统的结构
2.操作系统的工作方式:
3.操作系统内核中各级模块的相互关联
4.操作系统结构的独立性
5.高低版本的内核之间的区别:
分类:硬件中断和软件中断;
linux内核0.11版本:
中断的工作流程(入栈 执行 出栈):
jiffies:系统滴答,CPU内部有一个RTC时钟,会在上电的时候调用mktime函数算出从1970年1月1日0时开始到当前开机点所过的秒数。给mktime函数传来的时间结构体是由初始化时从rtc或者cmos中读出的参数,转化为时间存入全局变量中,并且会为jiffies所用。
jiffies是系统的一个滴答,一个滴答是10ms。每个滴答引发一个中断:
前置知识:
task_struct进程结构体(状态、时间片、优先级、tss结构体等;):
内存:
上电进行一些初始化之后会执行init/main.c中的main函数,里面进行内存的拷贝(引导)、内存初始化、trap初始化、块设备驱动初始化、字符设备驱动初始化、调度初始化(初始化task链表)、软盘初始化,然后转移到用户态,因为此处不能被抢占,进程在内核态运行是不能被抢占的。然后创建0号进程,进程执行打开标准输入/输出/错误控制台,然后再创建1号进程打开/etc/rc
文件,并执行shell(后面用另外一种方式又打开了一下避免没有被打开)。最后运行for(;;) pause();
在没有其他进程运行时,运行0号进程。
进程的创建是系统的调用(中断):
给当前要创建的进程分配一个进程号find_empty_process()
。然后就是对0号进程或者当前进程的task_struct和栈堆复制copy_process()
。
进程状态:
调度代码(优先级时间片轮转调度算法):
进程切换switch_to()
:
sleep_on() // 休眠任务链表,等待资源
wake_up() // 从不可中断状态变为运行状态
do_exit
销毁函数释放进程的代码段和数据段和堆栈段;内核代码:
kill信号:
BIOS/Bootloader:
由PC机的BIOS(0xFFFFO是BIOS存储的总线地址)把bootsect从在磁盘拿到了内存中的0x7c00地址,并且进行了一系列的硬件初始化和参数设置。
bootsect.s:磁盘引导块程序,在磁盘的第一个扇区中的程序(0磁道 0磁头 1扇区),被BIOS加载到0x7c00地址后,自己将自己剪切到0x9000地址处。
作用:首先将后续的setup.s代码从磁盘中加载到紧接着bootsect.s的地方 在显示屏上显示loading system 再将system(内核代码)模块加载到0x1000的地方最后跳转到setup.s中去运行。
setup.s:解析BIOS/BOOTLOADER传递来的参数(光标位置/显存大小/显示参数/硬盘参数/根文件系统),设置系统内核运行的LDT和IDT(中断描述符寄存器)和GDT全局描述符寄存器。设置中断控制芯片,进入保护模式运行,跳转到system模块的最前面的代码运行(head.s)。
head.s:加载内核运行时的各数据段寄存器,重新设置中断描述符表,开启内核正常运行时的协处理器资源,设置内存管理的分页机制,跳转到main.c开始运行。
main.c:
上电进行一些初始化之后会执行init/main.c中的main函数,里面进行内存的拷贝(引导)、内存初始化、trap初始化、块设备驱动初始化、字符设备驱动初始化、调度初始化(初始化task链表)、软盘初始化,然后转移到用户态,因为此处不能被抢占,进程在内核态运行是不能被抢占的。然后创建0号进程(init()函数
),进程执行打开标准输入/输出/错误控制台,然后再创建1号进程打开/etc/rc
文件,并执行shell(后面用另外一种方式又打开了一下避免没有被打开)。最后运行for(;;) pause();
在没有其他进程运行时,运行0号进程。
init()函数:
操作系统的移植过程:
总结:
硬件的信息存放在machine_desc结构体中。
3.x版本内核的引导过程从以下三个角度分析:
链接脚本:vmlinux.lds.S指定了代码段。
宏定义:#define MACHINE_START(_type, _name) xxxx,为了从代码段中获得硬件架构信息。
结构体machine_desc,用于Linux做设备板子的识别结构体,这些结构体被限定在了内存的某一块区域,并且通过UBOOT传过来的参数进行该结构体的配置,并且在移植Linux的时候,也要对结构体的变量进行赋值,并且在之后的启动或其他函数中对结构体的变量进行调用。
lookup_processor_type_data
__mmap_switched:将旧的地址转化为虚拟地址(代码重定义)。
setup_arch:创建CPU指令集描述结构体、从指定的内存中获取到该描述结构体、将获取到的GPU名字赋值给一个全局变量、使用当前函数进行UBOOT的taglist的参数解析、找到一个移植Linux时写的最适合的machine_desc结构体,并且返回。
static noinline int init_post(void):系统运行的第一个应用程序(根文件系统的挂接)。
概念:是磁盘管理的目录、是Linux中操作所有硬件设备的方式、系统的功能机制。
也是一个应用程序,Linux内核初始化之后,运行的第一个应用程序。
文件系统作用:
Android就是Linux多了个文件系统,实际上是多了lib和framework,包括glibc、OpenGL图形化库、media_framework媒体框架库、虚拟机、架构代码(对多种服务进行封装)。
busybox文件系统:
new_init_action()
run_actions()
__setup_param()
UBOOT传入了很多的参数,tagglist,被解析为多个setup段,存放在.init.setup的代码段中,形式为CMD字符串和命定对应的处理函数。在两个函数obsolete_checksetup(处理early为0的)和do_early_param(处理early不为0的)中进行了所有存放在.init.setup代码段的命令执行。针对各种setup段的CMD进行全局变量的赋值。
内核启动文件系统后,文件系统的工作流程:
内核启动的第一个应用程序是根文件系统,根文件系统首先在init_main()函数设置了一些信号,即使用run_actions执行某些信号,如重启、关机等。内核一般调用文件系统的时候不传入参数,就会运行parse_inittab()
函数来解析inittab。如果永和自己定义了就打开,如果没有配置就执行默认的配置(new_init_action()),这里做的事情就是创建init_action结构体节点,并且把inittab中所有的配置项解析的init_action节点形成一个init_action_list,每一个节点都是inittab的一个配置项,每个配置项都有自己的action和命令脚本。后面做的事情就是执行这些命令(有的执行一次 有的等待 有的一直运行...),最后根文件系统执行的就是shell一直循环等待用户的命令。
一个最小文件系统都需要什么东西:
Linux中使用文件系统都分那几个部分
文件系统的基本概念
磁盘中要有目录的映射,我们把磁盘分成盘片
每一个盘片都有一个文件系统的子系统(章节目录),由以下几部分组成:
高速缓冲区的管理要素
高速缓冲区工作流程
高速缓冲区中存储着对应的块设备驱动的数据,当从块设备中读取数据的时候,OS首先会从高速缓冲区中进行检索,如果没有则从块设备中读出数据,如果有并且是最新的,就直接和该高速缓冲区进行数据交互。
高速缓冲区和磁盘块一一对应
缓冲区可以分为低区和高区,低区(buffer_head结构体
)的某一块也对应高区的某一块,类似inode节点位图和i节点。
假如我们来写一个buffer.c
getblk()
get_hash_table()
文件与磁盘的映射结构
每一个盘片都有一个文件系统的子系统(章节目录),由以下几部分组成:
扇区:是一个长度为512B的数据块
在不同的文件系统中,扇区和盘块对应关系是不同的,0.11内核是两个扇区对应一个盘块,最大的文件系统为:1024B * 1024 * 8 = 8M;8M * 8 = 64M,当前linux最大支持64M的块设备大小
不管读取什么磁盘上的资源,都是先getblk(获取该资源对应的设备和块号的高速缓冲区),然后再bread(确认有效数据的高速缓冲区),最后进行区域内存的拷贝从从bh(buffer_head)的b_data数据区域拷贝到要用到数据的内存中(buffe(hd read(char * source char * desbuf uint size))
source --> bh->b_data
desbuf --> 硬盘
_bmap(struct m_inode * inode,int block,int create)
: 对文件进行磁盘映射,也就是给m_inode结构体中的unsigned short i_zone[9]
inode节点的磁盘块映射。i_zone前7个存放的是逻辑块号(7K内存),第八个是一次间接块号(前面是指针,这里就是指针的指针),指向一个存放512(1个逻辑块号是1k,short类型是2B,所以有512个)个逻辑块号的数组。第九个是二次间接块号(指针的指针的指针),指向512个一次间接块号。
从磁盘读写数据 信息 inode 等的流程:
super.c作用:
get_super
读取read_supper
释放put_supper
)sys_mount
sys_umount
mount_root
namei.c作用:根据输入字符进行文件目录的建立、删除、打开 节点建立 删除。
namei.c目的:文件系统中的文件操作(打开、权限、属性)、了解文件系统的命令操作(如chmod chown mknod)、c语言中内存区域的检索和管理。
链接文件在内核中就是给已存在的文件,添加一个dir_entry:
学习目的:
Linux内存使用情况(用户内存和内核内存分开):
内核内存 | 高速缓冲区(包括提供给BIOS的和显存) | [虚拟盘]主内存(用户内存) |
---|
内存管理名词:
虚拟内存的好处:
虚拟内存映射到物理内存的方式:
GDT从底向上依次为:null->代码段(内核的)->数据段(内核的)->系统段->状态段tss0(任务0)->局部表ldt0(任务0)->状态段tss1(任务1)->局部表ldt1(任务1)->状态段tss2->局部表ldt2......
分段:
分页:
内存管理主要实现了两个重要方式:
分页机制:缺页重读,在页目录和页表表项结构中,10位用于页目录寻址,10位用于页表寻址,其余12位的长度供其他权限使用,其中最低位P位是存在位,当P=1的时候该项可用,当目录表项或二级页表表项的P=0时,表示该项无效,通过这种机制实现了我的程序不用全部加载到内存,当用到的时候引发缺页中断再写进内存。倒数第二位R/W表示读还是写,倒数第三位U/S表示用户能用还是super权限能用。倒数第六位是Accessed位表示允不允许访问。倒数第7位表示Dirt位,如果置写了那么会执行同步操作
内存读写权限:用时拷贝。在A进程fork() B进程后,只是把A的虚拟内存copy给B,但此时A和B共用一段物理内存,并且把当前的共享内存设置为只读内存。一旦有A或B对这块内存进行写操作时,就会引发页面出错异常page_fault
。在该异常的中断服务函数中,就会首先取消共享内存的操作,并且给写进程复制一个新的物理页面(写多少复制多少),此时A和B就各自有一块要写的内存,然后设置该内存为可读写状态,然后返回重新进行刚才的写操作。
以上内存分段分页机制是在主内存中,也就是用户内存,在内核内存是不进行分段分页机制的。内核内存结构:
向量表 |
---|
vmalloc(连续的堆空间) |
DMA+常规区域映射区(比如kmalloc也是在这里分配,以页为单位,所以内存可能不连续) |
高端内存映射区(上面不够了从这里借) |
内核模块(xx.ko) |
slab 内存池:适合用在分配大量小对象的情景下(使用buddy算法管理)。创建kmem_cache_create();
ioremap: 物理地址映射为虚拟地址,建立了一个虚拟的页目录 页表等,把指定的物理地址放入建立好的页表关系中。
iounmap: 销毁掉ioremap建立的映射关系。IO读写中专门做寄存器读写的函数:readb
readw
readl
writeb
writel
,前面函数都是带内存屏蔽的,也就是这行代码等待前面代码执行完再执行(编译乱序情况下),不带内存屏蔽的是readb_relaxed
。
mmap: 做了内存和设备的关联,将用户空间的一段内存与设备关联,当用户访问这段内存的时候就会自动转化为对这段物理内存的访问。
munmap: 取消mmap做的内存和设备的关联。
程序在硬盘到执行做的处理:
copy_strings()
函数),设置新函数的sp,重新调整代码段字符设备驱动程序调用过程:用户app->Linux系统调用->虚拟文件系统->具体硬件操作。
写的.c驱动程序添加到sourceinsight中,写.c驱动程序的时候就可以基于内核提供的一些函数,编写代码会有代码补充什么的比较方便。与.c驱动文件在一块的还有个Makefile编译文件,一般结构为:
makefileKERN_DIR = (内核根目录)
all:
make -C $(KERN_DIR) M='pwd' modules
clean:
make -C $(KERN_DIR) M='pwd' modules clean
rm -rf modules.order
#生成char_drv_1.o文件 m表示不加入内核中 如果是y则表示加入内核中
obj-m += char_drv_1.o
Makefile文件在编译时,会通过根目录下的.config文件传入参数,编译的时候会生成auto.conf和autoconf.h文件,auto.conf是给编译用的表示要将那些程序编译到内核镜像中,autoconf.h是给c程序用的, 里面其实添加的宏定义。
写驱动的方法:
SMP概念:SMP是Symmetric Multi Processing的简称,意为对称多处理系统,内有许多紧耦合多处理器,这种系统的最大特点就是共享所有资源。在这种技术的支持下,一个服务器系统可以同时运行多个处理器,并共享内存和其他的主机资源。一个CPU的多个核可能运行着不同的进程,各种进程之间通信是一个需要解决的复杂问题,有以下几种解决方式:
request_irq();
取消free_irq();
定义一个工作队列struct work_struct kindlemem_work();
(是把所有的中断下半部放在一个线程中。还有线程中断threaded_irq
是把每个中断下半部都作为一个线程)。atomic_t atomic_a = ATOMIC_INIT(int i)
atomic_set(atomic_a, int i)
atomic_a = atomic_read(atomic_a)
atomic_add()
atomic_sub()
atomic_inc()
atomic_dec()
atomic_inc_and_test()
atomic_dec_and_test()
atomic_sub_and_test()
atomic_add_return()
set_bit()
clear_bit()
change_bit()
test_bit()
test_and_set_bit()
。缺点:只能对int做操作。spinlock_t lock;
初始化spin_lock_init(spinlock_t *lock);
锁定/获取锁spin_lock(spinlock_t *lock);
释放锁 spin_unlock(spinlock_t *lock);
锁之间不允许被调度出去,所以要关开中断。rwlock_t my_rwlock;
初始化rwlock_init(my_rwlock);
读锁定read_lock();
读解锁read_unlock();
写锁定write_lock();
写解锁write_unlock();
尝试获得write_trylock()
。seqlock_t *my_seqlock;
初始化seqlock_init()
获得顺序锁write_seqlock()
读开始read_seqbegin()
重读函数read_seqretry()
。rcu_read_lock();
读解锁rcu_read_unlock();
替换list_replace_rcu();
更新synchronize_rcu();
(阻塞函数) 释放复制的节点kfree();
。struct mutex kindle_mutex;
初始化mutex_init();
获取mutex_lock();
释放mutex_unlock();
。与锁的区别是允许有进程切换和临界区较大的时候。struct completion kindlemem_completion;
也允许上下文切换,等待的任务放在等待队列中。初始化init_completion();
等待完成量wait_for_completion()
唤醒第一个节点completion();
唤醒等待队列所有节点completion_all();
struct semaphore kindlemem_semaphore;
初始化sema_init()
获取down()
down_interruptible()
释放up()
。DMB: 数据内存屏蔽:保证所有DMB之前的内存访问行为完成。
DSB:数据同步屏蔽:保证DSB之前的所有指令完成操作。
ISB:指令同步屏蔽:刷新CACHE。
设计思想:同类型的子系统开发一个核心层代码,以输入子系统为例,主要涉及input_dev和input_handler。
不同鼠标键盘的驱动向核心层注册input_dev和input_handler,最终用户调用read/write一级一级的找到目标硬件设备的驱动。
输入子系统驱动
定时器使用方法
init_timer(struct timer_list xx_timer)
xx_timer.function=(void *)xx_timer_handle
add_timer(&xx_timer)
mod_timer(&xx_timer,jiffies+HZ/100)
MISC(多种多样的设备驱动)子系统,是Linux预留的分层结构,在写驱动时如果没有对应的核心层时所使用的,主设备号为10,从设备号可自定义。
对于platform驱动在platform_device中对用到的资源进行描述(IO/中断/mem/DMA),指定个名字,与platform_driver指定的名字匹配后执行platform_driver的probe函数,进行资源初始化给定fops等。
LCD驱动:核心层fmmem.c
触摸屏驱动:核心层input.c(input_dev和input_handler),事件驱动。优化:
硬件设计
系统构建
底层高级驱动设计
应用层设计
一、需求确定:
二、设计框架
内核信号项目-写一个自己的信号
也是一种中断,所以在system_call.s中会出现。
do_signal不是接受信号然后判断信号值,在中断中执行对应的函数,而是将对应的信号处理程序句柄插入到用户的堆栈中(具体的函数实现代码在文件系统中)。
uboot负责设置系统时钟、关闭中断、设置到svc32模式(超级保护模式,复位/软件中断进入)、初始化硬件、进行uboot代码的重定义、给内核进行参数配置(放到tagglist中)、做内核代码的重定义、启动内核。(如果bootloader比较大,做重定位到SDRAM)
在source insight中add代码,尤其arm架构中的只add和单板相关的代码:
uboot使用过程:
make xxx_config
,会生成config.mk给makefile编译使用,生成config.h给c语言使用。make
(依赖u-boot.lds文件生成u-boot.bin)执行的时候程序先执行start.S:
call_board_init_f()
运行nand flash的初始化操作移植过程:
grep "smdk2410" * -nR
:basharch/arm/include/asm/mach-types.h:1645:# define machine_is_smdk2410() (machine_arch_type == MACH_TYPE_SMDK2410)
arch/arm/include/asm/mach-types.h:1647:# define machine_is_smdk2410() (0)
board/samsung/smdk2410/Makefile:8:obj-y := smdk2410.o
boards.cfg:72:Active arm arm920t s3c24x0 samsung - smdk2410 David Müller <d.mueller@elsoft.ch>
有以下文件:mach-types.h:1645、mach-types.h:1647、Makefile:8、boards.cfg:72。
boards.cfg是板子配置表(名字、arch、cpu、soc等),复制粘贴一个目标板卡的,其他文件修改类似。最后配置一下make smdk2440
然后编译make
。大概看看错误,下面从头开始修改代码。
start.s 修改设置svc32模式(cpsr寄存器定义),看门狗寄存器位,失能中断,时钟分频,总线模式,启动ICACHE,SDRAM。
通过虚拟化技术可以将一个物理计算机分割为多个虚拟计算机,提高硬件的利用率。
目前全球排名前5的虚拟化软件公司: VMware、微软、红帽(RedHat) 、Oracle等
虚拟机由虚拟化层、虚拟层和管理层三部分组成。
作用:动态内存管理,动态分配对象之后自动释放资源,防止内存泄漏。
<memory>
中的一个智能指针,对对象有独有权,两个unique_ptr不能指向一个对象,不能进行复制操作只能进行移动操作。<memory>
中的一个智能指针,协助shared_ptr,解决shared_ptr指针循环引用问题。unique_ptr和shared_ptr两个智能指针的区别:
服务器端使用API函数流程:
socket()-->bind()-->listen()-->accept() -->recv ()-->close()
客户端流程使用API函数流程:
socket()-->connect()-->send ()-->close()
select模型:可以同时监听多个文件描述符上的IO事件,从而实现高效地处理并发IO操作。IO多路复用select函数,通过将需要监听的文件描述符以位图的形式传递给select函数,并设置超时时间,当有任何一个文件描述符上的IO事件发生时,select函数返回,开发者可以遍历所有文件描述符来处理就绪事件。可用于网络编程、高并发服务器、IO密集型应用等。
ARM64处理器内核地址空间布局
ARM64处理器异常级别
支持虚拟化的异常切换
同步异常:执行指令时生成的异常。
异步异常:中断IRQ、快速中断FIQ、系统错误SE。
异常处理:
Linux内核作为一个通用操作系统,包括实时进程、交互式进程、批处理进程。
shell# 显示file文件的前10行
head -n 10 file
# 显示file文件的后10行
tail -n 10 file
# 合并文件
cat file1 file2 > file3
# 显示大文件时用more,一次显示两行
more -2 file
# 显示当前登录用户的所有信息
who -a
# 显示当前操作系统的信息
uname -a
# 寻求帮助,用于查看命令/函数/文件的帮助信息
man
# 改变文件所有权(chown/chgrp)
chown [] 用户名 文件名称
# 更改文件所有者
chown -R 用户名 文件名
# 更改文件属于的组
chgrp 用户名 文件名
# 修改文件权限
chmod 777 文件名/文件夹
# 创建软链接(快捷方式)
ln 源文件 新文件
# 查看文件的i_node,如果两个文件是软链接关系的话,i_node值一样
ls -li 文件名
# 输入输出重定向(</>)
# '>'将程序的标准输出或标准错误输出重定向到指定的文件或设备
# '<'将标准输入流从键盘改为来自其他文件或其他命令的输出
# ubuntu软件包管理
apt-get
# 查看软件安装位置
dpkg -L 名称
# 或
which 名称
# 查看系统已经安装的包
dpkg --get-selections
# 硬盘发展IDE/SCSI/SATA
# linux文件系统常用的3种类型Ext2/Ext3/Ext4/XFS/JFS
# swap交换分区,类似虚拟内存,当内存不足时,把一部分硬盘虚拟为内存使用
# 挂载文件系统
mount [-t 文件系统类型] [-o 挂载选项,如读写权限访问控制等] 设备文件名 挂载点
# fsck检查及修改文件系统
fsck -C -t 文件系统名
# 在磁盘创建文件系统
mkfs --help
# 建立磁盘分区表
fdisk
# 压缩工具gzip(直接将原文件替换)
gzip 文件名
gzip -r 目录
# 解压
gzip -d 文件名
# 打包压缩工具tar
tar -cvf 生成的文件名.tar file1 file2 目录1 目录2
# 解压
tar -xf 生成的文件名.tar -C 保存的目录
# 其他压缩工具:bzip2/zip
# 显示当前用户执行过的命令历史记录
history
# 管理用户账号,比如修改账号名
usermod -l 新名字 原名字
# 查看用户信息: id命令
id 用户名
# 用户之间切换: su命令
su 用户名
# 监视进程 ps命令,显示当前所有在线用户和运行状态等
ps -aux
# 显示某一个用户的进程
ps -u 用户名
# 即使跟踪进程:top命令
top
# 向进程发送信号 kill向进程发送终止操作
kill PID号
# 调整进程的优先级nice/renice
# 使用ps命令查看进程nice值并降序排列
ps axo pid,comm,nice --sort=-nice
# eth和 ens 区别:一个物理网卡,一个是虚拟网络会话。
# 启动和关闭指定网卡
ifconfig 网卡名 up/down
# 配置IP地址
ifconfig 网卡名 地址
# 报告CPU统计数据: iostat命令,监控系统磁盘IO活动情况,读写速率等。
# I/O监控:iotop命令,内核输出的I/O使用情况,每个进程线程的带宽
# 报告CPU统计数据: mpstat命令
# 虚拟内存统计,使用情况、进程活动、磁盘I/O和CPU使用率
vmstat -a
本文作者:zzw
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 License 许可协议。转载请注明出处!