Linux内核分为五大部分
进程管理,内存管理,文件系统管理,设备管理和网络管理
应用程序对硬件设备的操作管理
设备驱动
系统调用是用户与内核的接口,设备驱动则是内核与硬件的桥梁
字符设备和块设备
Block Device和 Character Device
Block Device Driver是以固定大小长度来传送转移资料 ;Character Device Driver是以不定长度的字元传送资料 。
Block Device大致是可以随机存取(Random Access)资料的设备,如硬碟机或光碟机;
而Character Device刚好相反,依循先後顺序存取资料的设备,如印表机 、终端机等皆是。
如果一个硬件设备是以字符流的方式被访问的话,那就应该将它归于字符设备;反过来,如果一个设备是随机(无序的)访问的,那么它就属于块设备。
两种区别在于设备能否被随机访问数据
两种设备的三点区别
1.字符设备只能以字节为最小单位访问,而块设备以块为单位访问,例如512字节,1024字节等
2.块设备可以随机访问,但是字符设备不可以
3.字符和块没有访问量大小的限制,块也可以以字节为单位来访问
建立一个新的名叫 coffee
',主设备号为 12
和从设备号为 2
的设备文件
mkond 用来创建字符设备或者块设备
$mknod /dev/coffee c 12 2
主设备号用来分种类,从设备号用来区分同一种类的不同设备
c为创建字符设备
b为创建块设备
字符设备数据结构
struct cdev { struct kobject kobj; /字符设备嵌入的kobject 对象/ struct module owner; /驱动的拥有者*/ const struct file_operations ops; /驱动提供的文件操作方法(函数)接口/ struct list_head list; /字符设备链表/ dev_t dev; /字符设备设备号/ unsigned int count; /字符设备子设备个数*/ };
字符设备,字符设备驱动与用户空间访问关系图
由cdev结构体来描述字符设备,
dev_t来定义设备号,
通过file_operations来定义字符设备驱动提供给VFS接口函数
insmod 通过 register_chrdev_region( ) 或 alloc_chrdev_region( )来静态或者动态获取设备号;
通过cdev_init()建立cdev与file_operations之间的连接
void cdev_init(struct cdev *cdev, const struct file_operations *fops) { memset(cdev, 0, sizeof *cdev); //将整个结构体清零 INIT_LIST_HEAD(&cdev->list); //插入到list成员里面 kobject_init(&cdev->kobj, &ktype_cdev_default);//初始化成员 cdev->ops = fops;//与文件操作建立连接 }
cdev_alloc为分配cdev结构,并动态申请cdev内存,创建了一个cdev结构体
cdev_add向内核注册一个cdev结构
rmmod注销cdev,unregister_chardev_region来释放设备号
设备号相应操作
dev_t 高十二位表示主设备号,低二十位表示次设备号
MAJOR用来提取major 和 minor
还可以用__register_chrdev函数一步到位,注册设备号,初始化cdev以及cdev的注册都可以
register_chrdev函数
static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops) { return __register_chrdev(major, 0, 256, name, fops); }
可以看出调用了__register_chrdev函数
再看__register_chrdev函数内容
int __register_chrdev(unsigned int major, unsigned int baseminor,unsigned int count, const char *name, const struct file_operations *fops) { struct char_device_struct *cd; struct cdev *cdev; int err = -ENOMEM; cd = __register_chrdev_region(major, baseminor, count, name); if (IS_ERR(cd)) return PTR_ERR(cd); cdev = cdev_alloc();//动态申请cdev结构体空间 if (!cdev) goto out2; //初始化cdev结构体,将它归属于fops文件操作 cdev->owner = fops->owner; cdev->ops = fops; kobject_set_name(&cdev->kobj, "%s", name); //把cdev注册添加到内核当中 err = cdev_add(cdev, MKDEV(cd->major, baseminor), count); if (err) goto out; cd->cdev = cdev; //返回主设备号 return major ? 0 : cd->major; out: kobject_put(&cdev->kobj); out2: kfree(__unregister_chrdev_region(cd->major, baseminor, count)); return err; }
对于一个char类型的设备,我想对其进行read wirte 和io操作,那么我们通常会在内核驱动中实现一个file_operations结构体,然后分配主次设备号,调用cdev_add函数进行注册。从/proc/devices下面找到注册的设备的主次设备号,在用mknod /dev/char_dev c major minor 命令行创建设备节点。在用户空间open /dev/char_dev这个设备,然后进行各种操作。
在创建device设备时,如果定义了dev_t,那么会创建一个inode节点,并且绑定一个cdev,以及该cdev的一个默认的file_operations;如果针对该dev_t,定义了自己的file_operations,再调用cdev_add(),那么就会用自己定义的file_operations替换掉那个默认的file_operations.这样 device和cdev以及自己定义的file_operations就关联起来了。
/proc/devices 和/dev的区别,可以把/proc/devices看成驱动程序,只在内存中
/dev为我们的设备节点,用户通过/dev来访问内核的驱动
块设备驱动
块设备数据结构
磁盘类设备通用部分的数据结构描述,可以表示已分区或未分区的磁盘
块设备的读写请求放在特别的读写请求队列来实现,没有了像字符设备的read / wirte操作函数
bio数据结构
在文件系统和块设备子系统中来回的游动,把所需的数据来回搬运通常来讲,bio代表来自文件系统的一个请求,每个bio都代表不同的访问上下文,可能来自不同的文件系统或应用程序,所以bio对象包含了块设备处理一个请求它所需要的所有的信息
文件系统对文件的操作执行顺序
来自文件系统的一个个请求,会被添加到request_queue队列里面,这就是我们说的请求队列,实际上这个块设备驱动就会从这个请求队列里面读取request,然后进行处理,把处理发送到硬件设备中。
内核线程
内核线程只运行在内核态,只能使用PAGE_OFFSET以上的地址空间,不能访问用户空间,没有独立的地址空间
二号进程kthreadd用来创建内核线程
内核线程的一些补充:
1.内核线程永远运行于内核态绝不会跑到用户态去执行。
2.由于内核线程运行于内核态,所有它的权限很高,请注意这里说的是权限很高并不意味着它的优先级高,所有他可以直接做到操作页表,维护cache, 读写系统寄存器等操作。
3.内核线性是没有地址空间的概念,准确的来说是没有用户地址空间的概念,使用的是所有进程共享的内核地址空间,但是调度的时候会借用前一个进程的地址空间。
4.内核线程并没有什么特别神秘的地方,他和普通的用户任务一样参与系统调度,也可以被迁移到任何cpu上运行。
5.每个cpu都有自己的idle进程,实质上也是内核线程,但是他们比较特殊,一来是被静态创建,二来他们的优先级最低,cpu上没有其他进程运行的时候idle进程才运行。
6.除了初始化阶段0号内核线程和kthreadd本身,其他所有的内核线程都是被kthreadd内核线程来间接创建。
所有任务的祖先是0号进程,0号进程又创建了1号进程(init进程所有用户任务的祖先)和2号进程kthreadd(所有内核线程的祖先)。
每个cpu都有自己的idle进程,实质上也是内核线程,但是他们比较特殊,一来是被静态创建,二来他们的优先级最低,cpu上没有其他进程运行的时候idle进程才运行。
kkthreadd内线线程处理过程
先判断请求创建内核线程链表是否为空,是的话就schedule进入睡眠。反之则运行创建内核线程函数并移除创建内核线程请求链表。然后调用create_kthread创建一个内核线程来执行剩余的工作。
kernel/kthread.c create_kthread -> pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD)
LINUX磁盘
每个扇区512字节大小,64字节存储分区信息,每个分区信息16字节,所以一个扇区最多分4个区
每个磁盘分区由若干个扇区组成
带有*号的sda1说明是启动分区,存储主引导记录信息
分区信息存储方式由MBR和GPT两种方式
硬盘分区最常见分区类型为MBR,分别是主分区,扩展分区和逻辑分区,在电脑中,C盘为主分区,D ,F 等盘为逻辑分区。D F盘组合除了主分区则为扩展分区。在扩展分区的基础上建立我们的逻辑分区。
当我们建立了一个逻辑分区的时候,不能立即使用,要对新分区进行格式化(指将分区格式化为不同的文件系统)。格式化后会保留一些格式化类型的信息,所以会导致可用分区大小比分配的少
格式化后的分区还要挂载,要与文件存放的磁盘建立一个联系也就是挂载,在主磁盘中建立一个新分区的目录。挂载的目录应该为空,挂载后原文件夹中的文件会被隐藏
我们的根目录是 /
cd / 即进入根目录
media 和 mnt为我们的挂载点目录
下面操作为临时挂载
挂载命令为 mount /dev/sda5 /test 意思为将sda5挂载到test文件夹中
卸载命令为umount
LVM(Logical volume Manager,即逻辑卷管理)磁盘管理
满足了我们动态管理磁盘分区空间的问题
逻辑卷概念
①PE (Physical Extend) 物理拓展
②PV (Physical Volume) 物理卷
③VG (Volume Group) 卷组
④LV (Logical Volume) 逻辑卷
LVM我们以逻辑卷的方式呈现给上层,所有的操作就是创建一个LV,LV用来取代我们之前的分区,格式化我们的LV然后进行挂载操作。
要将硬盘格式化为PV,先把硬盘划分为一个个PE,PE默认大小为4M,是逻辑卷管理的最基本单位。然后创建VG(VG里面放PE),然后基于VG创建LV,选出部分PE组成LV。创建好LV后,就是格式化操作和mount挂载,LV的扩充缩减即使变化PE的数量,其过程不会丢失原始数据。
/proc系统
/proc 文件系统是Linux上的一种虚拟文件系统,存储的是当前内核运行状态的一系列特殊文件,用户可以通过这些文件查看有关系统硬件及当前正在运行进程的信息,甚至可以更改其中某些文件来改变内核的运行状态。
Linux文件系统
Linux靠文件索引号来辨别文件
虚拟文件系统(VFS)
VFS框架
用户调用对文件的操作都通过VFS 来实现具体操作
文件系统由超级块(数据结构)来描述。
要进入VFS的管理,则要进行注册,填写file_system_type数据结构,此结构描述文件系统名称和指向VFS的read函数的地址
VFS只管理挂载到系统中的真实文件系统
系统中所有超级块由super_blocks链表以双向环形链表的形式链接在一起
文件系统设计由4个对象
超级块,索引节点,目录项和文件
VFS的索引节点,当一个文件被首次访问时,内存会组装相应的索引节点对象,并进行初始化。
打开文件后,文件相关的“上下文”(file数据结构),索引节点,目录对象都已经生成。
同一个文件系统中,每个索引节点号是唯一的。inode有两个设备号,i_dev(常规文件设备号)和i_rdev(某一设备号)
目录项对象是逻辑意义上的文件,在磁盘中没有对应的磁盘数据结构(dentry)
文件对象是已打开的文件在内存中的表示,用于建立进程和磁盘上文件的对应关系
由sys_open()现场创建,sys_close()销毁
文件系统中的缓冲区
buffer:存放要写入磁盘的数据
cache:存放磁盘中读出的数据
page cache:针对文件系统,是文件的缓存,文件层面上的数据缓存到这里
buffer cache:针对磁盘块的缓冲,在没有文件系统的情况下,直接对磁盘进行操作的数据会缓存到buffer cache中
dentry cache:把路径转换为索引节点
这些缓存的作用都是致力于加快索引节点的查找
文件系统的读写
读写的单位是块,块的大小是2的n次方个扇区,一个扇区大小为512字节。
##
IO端口与IO内存
IO端口与cpu的空间相互独立,互不干扰。
IO内存该空间处在CPU空间范围内,与普通内存没什么区别,要操控它就把它映射到cpu地址空间内
POSIX(可移植操作系统接口) 是一个标准
系统调用是通向操作系统本身的接口,是面向底层硬件的.
互斥锁和自旋锁
自旋锁不会引起调用者的睡眠,会使调用者一直申请这个锁的使用,会一直占者cpu,使cpu效率降低,直到申请到锁的使用。而互斥锁则可以使cpu先运行下一个申请线程,不必一直申请互斥锁的资源
VFS提供三类接口:
-
和POSIX系统调用的接口 即实现open/read/write的操作的接口。
-
和底层介质的接口 即下接块设备层的接口。
-
如何管理自身 即何时以及如何操作VFS数据结构inode,dentry,mount等对象。
一个文件系统如果能实现上面三类接口,那它就是个完整的文件系统了。
重点在于红色部分,其他非必须实现。