uboot -> kernel -> 根文件系统。
uboot第一阶段属于汇编阶段:
定义入口(start.S):uboot中因为有汇编阶段参与,因此不能直接找main.c。
设置异常向量:当硬件发生故障的时候CPU会强制PC指针指向对应的异常入口执行代码。
设置CPU为SVC模式(设置CPU速度、时钟频率和中断控制寄存器)。
初始化内存控制器(MMU),实现虚拟地址到物理地址的映射。
跳转到lowlevel_init函数,关看门狗,供电锁存,时钟初始化。
初始化堆栈,DDR。
uboot第二阶段属于C语言阶段:
uboot的第二阶段就是要初始化剩下的还没被初始化的硬件。主要是SoC外部硬件(譬如iNand、网卡芯片····)、uboot本身的一些东西(uboot的命令、环境变量等····)。
调用一系列的初始化函数:init_sequence里的init函数是一个函数指针数组,数组中存储了很多个函数指针,会去调用其他初始化函数。
cpu_init(CPU内部的初始化),board_init(x210开发板相关的初始化),interrupt_init(初始化定时器),env_init(环境变量有关的初始化),
init_baudrate(初始化串口通信的波特率),serial_init(初始化串口),display_banner(用来串口输出显示uboot的信息)。
uboot启动内核:
启动内核第一步:加载内核到DDR中,boot只需要从SD卡的kernel分区去读取内核镜像到DDR中即可。
启动内核第二步:校验内核格式(zImage格式,uImage格式)。
启动内核第三步:内核传参。
内核启动过程:
内核启动的汇编阶段:
__lookup_processor_type:读取出硬件的CPU ID号,__lookup_machine_type:本函数校验的是机器码,
__vet_atags:用来校验uboot通过tag给内核传的参数。
内核启动C语言阶段:
lockdep_init:锁定依赖,是一个内核调试模块,处理内核自旋锁死锁问题相关的。
boot_init_stack_canary:用来防止栈溢出。
cgroup_init_early:control group,内核提供的一种来处理进程组的技术。
local_irq_disable:屏蔽当前CPU上的所有中断。
lock_kernel:获得大内核锁,该锁可以用来锁定整个内核。
page_address_init:函数初始化高端内存页表池的链表。
打印内核版本信息。
最后会执行init进程,init进程刚开始运行的时候是内核态,然后他自己运行了一个用户态下面的程序后把自己强行转成了用户态。
init进程会去挂载根文件系统,打开控制台,访问一个设备,就要去打开这个设备对应的文件描述符。譬如/dev/fb0这个设备文件就代表LCD显示器设备。
根文件系统:
根文件系统是特殊用途的文件系统,文件系统是一些代码,是一套软件,这套软件的功能就是对存储设备的扇区进行管理,将这些扇区的访问变成了对目录和文件名的访问.
操作系统是运行在计算机上最重要的一种软件
,它管理计算机的资源和进程以及所有的硬件和软件。它为计算机硬件和软件提供了一种中间层,使应用软件和硬件进行分离。
进程管理
: 进程管理的主要作用就是任务调度,在单核处理器下,操作系统会为每个进程分配一个任务。
内存管理
:内存管理主要是操作系统负责管理内存的分配、回收。
设备管理
:根据确定的设备分配原则对设备进行分配,使设备与主机能够并行工作。
文件管理
:有效地管理文件的存储空间,合理地组织和管理文件系统。
提供用户接口
:操作系统提供了访问应用程序和硬件的接口。
进程是系统中正在运行的一个程序,程序一旦运行就是进程,进程可以看成程序执行的一个实例。
线程是CPU独立运行和独立调度的基本单位,一个进程可以拥有多个线程,线程是进程的一个实体,是进程的一条执行路径。
因为进程拥有独立的堆栈空间和数据段,所以每当启动一个新的进程必须分配给它独立的地址空间,系统开销比较大。
而线程不一样,线程拥有独立的堆栈空间,但是共享数据段,它们彼此之间使用相同的地址空间,共享大部分数据,比进程更节俭,开销比较小,切换速度也比进
程快,效率高。
通信机制上面,正因为进程之间互不干扰,相互独立,进程的通信机制相对很复杂,譬如管道,信号,消息队列,共享内存,套接字等通信机制,而线程由于
共享数据段所以通信机制很方便。
进程优点:每个进程互相独立,不影响主程序的稳定性,子进程崩溃没关系;
进程缺点:需要跨进程边界,如果有大数据量传送,就不太好,适合小数据量传送、密集运算多进程调度开销比较大。
线程优点:能适当提高程序的执行效率,能适当提高资源的利用率。
线程缺点:每个线程与主程序共用地址空间,受限于2GB地址空间;
当频繁创建和销毁的时候,当然是线程。
当考虑的安全性的问题时候,是进程.
提高cpu利用率,是线程。
新建状态:新创建了一个线程对象。
就绪状态:线程只等待获取CPU的使用权。
运行状态:就绪状态的线程获取了CPU使用权。
阻塞状态:阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。
死亡状态:线程执行完了或者因遇到error或exception退出了run()方法,该线程结束生命周期。
孤儿进程:指的是在其父进程执行完成或被终止 后仍继续运行的一类进程。
僵尸进程:如果子进程退出,而父进程并没有调用 wait() 或 waitpid()去回收的一类进程。
守护进程:是运行在后台的一种特殊进程。守护进程一般在系统启动时开始运行,除非强行终止,否则直到系统关机都保持运行。
无名管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。
有名管道 (named pipe) :有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
高级管道(popen):将另一个程序当做一个新的进程在当前程序进程中启动,则它算是当前程序的子进程,这种方式我们成为高级管道方式。
消息队列( message queue ) :消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。
套接字( socket ) :套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。
进程上文:其是指进程由用户态切换到内核态是需要保存用户态时cpu寄存器中的值,进程状态以及堆栈上的内容,即保存当前进程的进程上下文,
以便再次执行该进程时,能够恢复切换时的状态,继续执行。
进程下文:其是指切换到内核态后执行的程序,即进程运行在内核空间的部分。
防止不同进程同一时刻在物理内存中运行而对物理空间的争夺采用虚拟地址空间。虚拟内存使得不同进程运行过程中,
每个进程只把自己目前需要的虚拟内存空间映射存储到物理空间中。
实际上,每个进程创建时,内核只为进程创建虚拟内存的布局,并不立即把虚拟内存和磁盘文件
之间的映射拷贝到物理内存中,等到运行时才会来拷贝数据。
对内存的分配和管理,也就是平时应用层malloc和内核层vmalloc、kmalloc之类的内存申请的管理
虚拟内存和物理内存之间的转换
因为在内核中操作的都是虚拟地址,内核访问不到物理地址,只能通过ioremap映射为虚拟地址,内核才能访问此内存空间
为了安全,在CPU的一些指令中,有的指令如果用错,就会导致整个系统崩坏,分了内核态和
用户态之后,当用户需要操作这些指令的时候,内核为其提供了API,可以通过系统调用陷入
内核,让内核去执行这些操作。
死锁产生的原因大致有两个:资源竞争和程序执行顺序不当
互斥条件:每个资源都被分配给了一个进程或者资源是可用的。
保持和等待条件:已经获取资源的进程被认为能够获取新的资源。
不可抢占条件:分配给一个进程的资源不能强制的从其他进程抢占资源,它只能由占有它的进程显示释放。
循环等待:死锁发生时,系统中一定有两个或者两个以上的进程组成一个循环,循环中的每个进程都在等待下一个进程释放的资源。
insmod调用init函数,rmmod调用exit函数。设计时需要严格记住相关内存的操作后必须在exit中进行释放,
避免内存泄漏。如存储,ioremap,定时器,工作队列等等。卸载失败原因是因为有进程正在使用该模块,这跟我们在windows下拔出正在使用的U盘类似。
该机制有助于缩短模块的开发周期。
由于内核空间和用户空间是不能互相访问的,如果需要访问就必须借助内核函数进行数据读写。
copy_to_user():完成内核空间到用户空间的复制。
copy_from_user():是完成用户空间到内核空间的复制。
一般用于file_operations结构里的read,write,ioctl等内存数据交换作用的函数。当然,如果ioctl没有用到内存数据复制,那么就不会用到这两个函数。
mmap函数实现把一个文件映射到一个内存区域,从而我们可以像读写内存一样读写文件,他比单纯调用read/write也要快上许多。
在某些时候我们可以把内存的内容拷贝到一个文件中实现内存备份,当然,也可以把文件的内容映射到内存来恢复某些服务。
另外,mmap实现共享内存也是其主要应用之一,mmap系统调用使得进程之间通过映射同一个普通文件实现共享内存。
系统调用。用户空间进程通过系统调用进入内核空间,访问指定的内核空间数据;
)驱动程序。用户空间进程可以使用封装后的系统调用接口访问驱动设备节点,以和运行在内核空间的驱动程序通信;
共享内存mmap。在代码中调用接口,实现内核空间与用户空间的地址映射,在实时性要求很高的项目中为首选,省去拷贝数据的时间等资源,但缺点是不好控制;
copy_to_user()、copy_from_user(),是在驱动程序中调用接口,实现用户空间与内核空间的数据拷贝操作,应用于实时性要求不高的项目中。
设备驱动连接操作系统和硬件。设备驱动程序是一种可以使计算机与设备进行通信的特殊程序,可以说相当于硬件的接口。操作系统只有通过这个接口,
才能控制硬件设备的工作。安装在操作系统中的驱动程序可以完成设备的初始化和释放,进行外部数据和操作系统的通信和数据交互,控制硬件的行为,
并检查设备可能出现的故障并报错。
Linux将硬件设备分为3大类,分别是字符设备、块设备和网络设备。字符设备是指那些能一个字节一个字节读取数据的设备,如键盘鼠标等,
常见的SPI/I2C/UART默认也是字符设备。块设备与字符设备类似,一般是像磁盘一样的设备。网络设备主要负责主机之间的数据交换。
与字符设备和块设备完全不同,网络设备主要是面向数据包的接收和发送而设计的。
互斥锁:mutex,保证在任何时刻,都只有一个线程访问该资源,当获取锁操作失败时,线程进入阻塞,等待锁释放。
读写锁:rwlock,分为读锁和写锁,处于读操作时,可以运行多个线程同时读。但写时同一时刻只能有一个线程获得写锁。
自旋锁:spinlock,在任何时刻只能有一个线程访问资源。但获取锁操作失败时,不会进入睡眠,而是原地自旋,直到锁被释放。
这样节省了线程从睡眠到被唤醒的时间消耗,提高效率。
条件锁:就是所谓的条件变量,某一个线程因为某个条件未满足时可以使用条件变量使该程序处于阻塞状态。一旦条件满足了,即可唤醒该线程(常和互斥锁配合使用)。
注册一个字符设备驱动有两种方法:
1) void cdev_init(struct cdev *cdev, struct file_operations *fops)
该注册函数可以将cdev结构嵌入到自己的设备特定的结构中。cdev是一个指向结构体cdev的指针,而fops是指向一个类似于file_operations结构(可以是file_operations结构,但不限于该结构)的指针.
2) int register_chrdev(unsigned int major, const char *namem , struct file)operations *fopen);
该注册函数是早期的注册函数,major是设备的主设备号,name是驱动程序的名称,而fops是默认的file_operations结构(这 是只限于file_operations结构)。对于register_chrdev的调用将为给定的主设备号注册0-255作为次设备号,并为每个 设备建 立一个对应的默认cdev结构。
insmod调用init函数,rmmod调用exit函数。这两个函数在设计时要注意什么?卸载模块时曾出现卸载失败的情形,原因是存在进程正在使用模块,检查代码后发现产生了死锁的问题。
要注意在init函数中申请的资源在exit函数中要释放,包括存储,ioremap,定时器,工作队列等等。也就是一个模块注册进内核,退出内核时要清理所带来的影响,带走一切不留下一点痕迹。