24小时学通Linux内核之如何处理输入输出操作

简介:   真的是悲喜交加呀,本来这个寒假早上8点都去练车,两个小时之后再来实验室陪伴Linux内核,但是今天教练说没名额考试了,好纠结,不过想想就可以睡懒觉了,哈哈,自从大三寒假以来还没睡过懒觉呢,现在也有更多的时间来分享自己学习Linux内核的感受,前几天觉得就是自己也有些不懂的,相信大家看了也是很模糊,以后我会标志出来自己不懂的,希望大神们指教,也希望大家多多指点,共同攻克Linux内核,今天将讲到处理器是如何与其它设备进行交互的,内核又是如何响应和控制这些交互的,今天内容不多但是很关键,写的不好希望大家批评,纯手打。

  真的是悲喜交加呀,本来这个寒假早上8点都去练车,两个小时之后再来实验室陪伴Linux内核,但是今天教练说没名额考试了,好纠结,不过想想就可以睡懒觉了,哈哈,自从大三寒假以来还没睡过懒觉呢,现在也有更多的时间来分享自己学习Linux内核的感受,前几天觉得就是自己也有些不懂的,相信大家看了也是很模糊,以后我会标志出来自己不懂的,希望大神们指教,也希望大家多多指点,共同攻克Linux内核,今天将讲到处理器是如何与其它设备进行交互的,内核又是如何响应和控制这些交互的,今天内容不多但是很关键,写的不好希望大家批评,纯手打。

  Linux内核是如何将软硬件结合起来的呢?这里我们将一起探究内核与周围硬件主要是文件IO和硬件设备之间的关系,来解释这个问题。处理器与周围设备的通信依赖于一系列的电路电线,总线就是具有类似功能的电线,设备与处理器通信主要是通过地址总线,数据总线,控制总线来实现,这里在学习单片机原理的时候也提到过,这里对系统的基本结构就不多说了,觉得更新快,不好讲解,也没什么好总结的,大家看看相关书籍就行。了解到设备可以当做文件系统中的文件来处理,其细节都可隐藏在内核中,而对应用程序员透明,当进程对设备文件应用某一系统调用的时候,只要将这一系统调用转换成某种设备函数就足够了,其中设备驱动程序定义了这些函数,接下来看看这些设备类型。其中应用层,文件系统层,通用块设备层和设备驱动程序之间的关系如下图,这里贴出来供大家了解一下。读写块设备如下:

  先介绍块设备,设备驱动在驱动程序初始化时为自己注册,将这个驱动程序加入内核的驱动程序表中,并将设备号映射到数据结构block_device_operations中,数据结构block_device_operations包含了系统中启动和停止给设定块设备的函数(在include/linux/fs.h上。

struct block_operations{
    int (*open) (struct inode *,struct file *);
    int (*release) (struct inode *,struct file*);  //open()和release()都是同步的
    int (*ioctl) (struct node *,struct file *,unsigned, unsigned long);
    int (*media_changed) (struct gendisk *);
    int (*revalidate_disk) (struct gendisk *);
    struct module *owner;
};

  从处理器的角度来看,在适合的cidao4上定位磁头并将磁盘转到相应的块要花费相当长的时间,这种延迟迫使内核实现了系统请求队列,在Linux2.6中,每个块设备都有自己的请求队列,以便管理对该设备的IO口请求,进程只有在获得请求队列锁之后才能 更新设备的请求队列,让我们先来看看request_queue结构(这些代码都是自己敲出来的,然后分析,分析不好的请各位大神批评改正。)这些代码都可以在include/linux/blkdev.h中查看。

struct request_queue
{
    struct list_head queue_head;  //指向请求队列对首的指针
    struct request  *last_merge;  //加到请求队列的最后一个请求
    elevator_t  elevator;      //这个不懂,求大神指struct request_list rq;     //由两个wait_queue组成,分别用于块设备读请求队列和写请求队列
...
    request_fn_proc *request_fn;
    merge_request_fn *back_merge_fn;
    merge_request_fn *front_merge_fn;
    merge_requests_fn *merge_requests_fn;
    make_request_fn *make_request_fn;
    prep_rq_fn *prep_rq_fn;
    unplug_fn *unplug_fn;
    merge_bvec_fn *merge_bvec_fn;
    activity_fn *activity_fn;      //定义调度程序相关函数来控制如何管理块设备的请求
...
    struct time_list unplug_timer;
    int unplug_thresh;
    unsigned long  unplug_delay;
    struct work_struct unplug_work;

    struct backing_dev_info backing_dev_info;    //用于去掉设备的IO调度函数

...
    void *queuedata;

    void *activity_data;  //这些是对设备和设备驱动程序相关的队列进行管理

...
    unsigned long  bounce_pfn;
    int  bounce_gfp;    //是指内核将高端内存缓存冲区的IO请求copy到低端内存缓冲区去

    unsigned long queue_flags;//变量queue_flags存储一个或者多个队列标志,参见下表格。
标志名称 功能
QUEUE_FLAG_CLUSTER 将介几个段合成一个 
QUEUE_FLAG_QUEUED 使用通用标志队列
QUEUE_FLAG_STOPPED 队列被停止
QUEUE_FLAG_READFULL 读队列已经满了
QUEUE_FLAG_WRITEFULL 写队列已经满了
QUEUE_FLAG_DEAD 队列被撤销
QUEUE_FLAG_REENTER 避免重入
QUEUE_FLAG_PLUGGED 插入队列

 

 

 

 

 

 

 

 

 

spinlock_t *queue_lock;

struct kobject kobj;

unsigned long nr_requests;  
unsigned int nr_congestion_on;
unsigned int nr_congestion_off;
unsigned short max_sectors;
unsigned short max_phys_segments;
unsigned short max_hw_segments;
unsigned short hardsect_size;
unsigned int max_segment_size;

unsigned long seg_boundary_mask;
unsigned long dma_alignment;

struct blk_queue_tag *queue_tags;

atomic_t refcnt;

unsigned int in_flight;

unsigned int sg_timeout;
unsigned int sg_reserved_size;  //前面这些变量定义了请求队列中可管理的资源
};

  Linux内核通过在设备的_init函数中调用下列函数来初始化块设备的请求队列,这些函数中,,可以看出请求对了内部的细节和相关帮助教程,在现在的Linux2.6内核中,每个块设备控制自己的锁,并且将自旋锁作为第二个参数来传递,其中第一个参数是块设备驱动程序提供的请求函数,下面的代码在drivers/block/11_rw_blk.c中查看得到。

request_queue_t *blk_init_queue(request_fn_proc *rfn,spinlock_t *lock)
{
    request_queue_t *q;
    static int printed;
    
    q = blk_alloc_queue(GFP_KERNEL);  //从内核的内存中分配空间队列,内容为0
    if(!q)
    return NULL;

    if(blk_init_free_list(q))  //初始化请求清单
       goto out_init;    //表示一直怕goto,懂但不会if(!printed){
       printed = 1;
       printk("Using %s io scheduler\n",chosen_elevator->elevator_name);
    }

    if(elevator_init(q,chosen_elevator))  //这是个初始化的函数
       goto out_elv;

    q->request_fn = rfn;
    q->back_merge_fn = 11_back_merge_fn;
    q->front_merge_fn = 11_front_merge_fn;
    q->merge_requests_fn = 11_merge_requests_fn;
    q->prep_rq_fn = NULL;
    q->unplug_fn = generic_unplug_device;
    q->queue_flags = (1<<QUEUE_FLAG_CLUSTER);
    q->queue_lock = lock;    //上述赋值是将电梯调度程序相关的函数与该队列关联

    blk_queue_segment_boundary(q,0xffffffff);  //检查是否满足最小尺寸

    blk_queue_make_request(q,__make_request)  //设置驱动从队列删除
    blk_queue_max_segment_size(q,MAX_SEGMENT_SIZE);  //初始化归并段的上限
    
    blk_queue_max_hw_segments(q,MAX_HW_SEGMENTS);  //初始化物理设备可以处理的最大段数
    blk_queue_max_phys_segments(q,MAX_PHYS_SEGMENTS);  初始化每一请求的最大物理数目段
    
    return q0;  //返回已经初始化的队列
    out_elv:
    blk_cleanup_queue(q);
    out_init:
    kmem_cache_free(requestq_cachep,q);
    return NULL;  //错误事件中清除内存的一个例程
}

  

  代码实在太难,我知道的也只是皮毛,那些都需要好好体会,如若有补充的希望各路大神能够多加改正我的缺点,现在来看看设备操作,基本 的通用块设备有open,close,ioctl以及request函数,请求队列不能直接被访问,但是可以通过一组帮助例程来访问,如下:

struct request *elv_next_request(request_queue_t *q)

这个帮助函数返回并指向下一个请求结构的指针,驱动程序可以通过查看该元素来收集所有信息,以确定它的的大小方向以及该请求相关的任何其他自定义操作,之后通过end_request()想内核报告这一信息:

void end_request(struct request *req,int uptodate)  //在请求队列中传递elev_next_request()获得的参数
{
    if(!end_that_request_first(req,uptpdate,req->hrad_cur_sectors))  //传输适合的扇区数
    {
        add_disk_randomness(req->rq_disk);  //加入系统熵池,表示不懂,求大神指教,
        blkdev_dequeue_request(req);  //删除请求结构
        end_that_request_last(req);  //收集统计信息并且释放可用的数据结构
    }
}

  下面来介绍一下其它各种设备,与块设备不同,字符设备用来传送数据流,所有串行设备都是字符设备,与字符设备类似,网络设备的数据在物理层上串行传输,而时钟设备是基于硬件脉搏跳动的设备,其实就是时钟相关的,还有那终端设备,这里就稍微提及一下。因为这些都和输入输出相关,大家只要有个印象就行了。

  

  小结

  结束了分析代码之旅,小结一下今天主要的内容,今天主要分享的是Linux内核是如何处理输入输出操作的,具体讨论了Linux是如何表示块设备和它的接口的,也介绍了Linux调度程序并且重点分析了请求队列,上述敲的代码,我也还有好多不懂,只能自己慢慢去体会了,希望各路大神看了之后能够稍加提醒一下,哎,反正这个寒假没啥事了,就一直和内核作伴吧,这些写的不好,以后继续努力,fighting~

 

  版权所有,转载请注明转载地址:http://www.cnblogs.com/lihuidashen/p/4244330.html

相关文章
|
16天前
|
算法 Linux 调度
深入理解Linux内核调度器:从基础到优化####
本文旨在通过剖析Linux操作系统的心脏——内核调度器,为读者揭开其高效管理CPU资源的神秘面纱。不同于传统的摘要概述,本文将直接以一段精简代码片段作为引子,展示一个简化版的任务调度逻辑,随后逐步深入,详细探讨Linux内核调度器的工作原理、关键数据结构、调度算法演变以及性能调优策略,旨在为开发者与系统管理员提供一份实用的技术指南。 ####
57 4
|
20天前
|
缓存 算法 Linux
深入理解Linux内核调度器:公平性与性能的平衡####
真知灼见 本文将带你深入了解Linux操作系统的核心组件之一——完全公平调度器(CFS),通过剖析其设计原理、工作机制以及在实际系统中的应用效果,揭示它是如何在众多进程间实现资源分配的公平性与高效性的。不同于传统的摘要概述,本文旨在通过直观且富有洞察力的视角,让读者仿佛亲身体验到CFS在复杂系统环境中游刃有余地进行任务调度的过程。 ####
40 6
|
5天前
|
缓存 网络协议 Linux
深入探索Linux操作系统的内核优化策略####
本文旨在探讨Linux操作系统内核的优化方法,通过分析当前主流的几种内核优化技术,结合具体案例,阐述如何有效提升系统性能与稳定性。文章首先概述了Linux内核的基本结构,随后详细解析了内核优化的必要性及常用手段,包括编译优化、内核参数调整、内存管理优化等,最后通过实例展示了这些优化技巧在实际场景中的应用效果,为读者提供了一套实用的Linux内核优化指南。 ####
23 1
|
11天前
|
算法 Linux 开发者
Linux内核中的锁机制:保障并发控制的艺术####
本文深入探讨了Linux操作系统内核中实现的多种锁机制,包括自旋锁、互斥锁、读写锁等,旨在揭示这些同步原语如何高效地解决资源竞争问题,保证系统的稳定性和性能。通过分析不同锁机制的工作原理及应用场景,本文为开发者提供了在高并发环境下进行有效并发控制的实用指南。 ####
|
19天前
|
缓存 资源调度 安全
深入探索Linux操作系统的心脏——内核配置与优化####
本文作为一篇技术性深度解析文章,旨在引领读者踏上一场揭秘Linux内核配置与优化的奇妙之旅。不同于传统的摘要概述,本文将以实战为导向,直接跳入核心内容,探讨如何通过精细调整内核参数来提升系统性能、增强安全性及实现资源高效利用。从基础概念到高级技巧,逐步揭示那些隐藏在命令行背后的强大功能,为系统管理员和高级用户打开一扇通往极致性能与定制化体验的大门。 --- ###
50 9
|
18天前
|
缓存 负载均衡 Linux
深入理解Linux内核调度器
本文探讨了Linux操作系统核心组件之一——内核调度器的工作原理和设计哲学。不同于常规的技术文章,本摘要旨在提供一种全新的视角来审视Linux内核的调度机制,通过分析其对系统性能的影响以及在多核处理器环境下的表现,揭示调度器如何平衡公平性和效率。文章进一步讨论了完全公平调度器(CFS)的设计细节,包括它如何处理不同优先级的任务、如何进行负载均衡以及它是如何适应现代多核架构的挑战。此外,本文还简要概述了Linux调度器的未来发展方向,包括对实时任务支持的改进和对异构计算环境的适应性。
38 6
|
18天前
|
缓存 Linux 开发者
Linux内核中的并发控制机制:深入理解与应用####
【10月更文挑战第21天】 本文旨在为读者提供一个全面的指南,探讨Linux操作系统中用于实现多线程和进程间同步的关键技术——并发控制机制。通过剖析互斥锁、自旋锁、读写锁等核心概念及其在实际场景中的应用,本文将帮助开发者更好地理解和运用这些工具来构建高效且稳定的应用程序。 ####
35 5
|
19天前
|
算法 Unix Linux
深入理解Linux内核调度器:原理与优化
本文探讨了Linux操作系统的心脏——内核调度器(Scheduler)的工作原理,以及如何通过参数调整和代码优化来提高系统性能。不同于常规摘要仅概述内容,本摘要旨在激发读者对Linux内核调度机制深层次运作的兴趣,并简要介绍文章将覆盖的关键话题,如调度算法、实时性增强及节能策略等。
|
20天前
|
存储 监控 安全
Linux内核调优的艺术:从基础到高级###
本文深入探讨了Linux操作系统的心脏——内核的调优方法。文章首先概述了Linux内核的基本结构与工作原理,随后详细阐述了内核调优的重要性及基本原则。通过具体的参数调整示例(如sysctl、/proc/sys目录中的设置),文章展示了如何根据实际应用场景优化系统性能,包括提升CPU利用率、内存管理效率以及I/O性能等关键方面。最后,介绍了一些高级工具和技术,如perf、eBPF和SystemTap,用于更深层次的性能分析和问题定位。本文旨在为系统管理员和高级用户提供实用的内核调优策略,以最大化Linux系统的效率和稳定性。 ###
|
19天前
|
Java Linux Android开发
深入探索Android系统架构:从Linux内核到应用层
本文将带领读者深入了解Android操作系统的复杂架构,从其基于Linux的内核到丰富多彩的应用层。我们将探讨Android的各个关键组件,包括硬件抽象层(HAL)、运行时环境、以及核心库等,揭示它们如何协同工作以支持广泛的设备和应用。通过本文,您将对Android系统的工作原理有一个全面的认识,理解其如何平衡开放性与安全性,以及如何在多样化的设备上提供一致的用户体验。