linux内核设计与实现的读书笔记
- 格式:doc
- 大小:16.19 KB
- 文档页数:4
Linux内核console设备实现详解【转】本⽂基于Linux-4.141.earlyconearly console,顾名思义,他表⽰的就是早期的console设备,主要⽤于在系统启动阶段的内核打印的输出,由于linux内核实际设备驱动模型还没有加载完成,所以早期的启动信息需要⼀个特殊的console⽤于输出log。
在系统初始化时通过cmdline参数来解析,代码如下:./init/main.c:static int __init do_early_param(char *param, char *val,const char *unused, void *arg){const struct obs_kernel_param *p;for (p = __setup_start; p < __setup_end; p++) {if ((p->early && parameq(param, p->str)) ||(strcmp(param, "console") == 0 &&strcmp(p->str, "earlycon") == 0)) {if (p->setup_func(val) != 0)pr_warn("Malformed early option '%s'\n", param);}}/* We accept everything at this stage. */return 0;}void __init parse_early_options(char *cmdline){parse_args("early options", cmdline, NULL, 0, 0, 0, NULL,do_early_param);}整个流程如下,系统启动阶段会读取cmdline,并且解析cmdline参数名字为earlycon的参数,然后执⾏do_early_param操作,其中的关键是调⽤p->setup_func,这个也就是earlycon驱动实现的内容,param_setup_earlycon函数:⾸先来看内核实现的earlycon驱动:./drivers/tty/serial/earlycon.c:static int __init param_setup_earlycon(char *buf){int err;* Just 'earlycon' is a valid param for devicetree earlycons;* don't generate a warning from parse_early_params() in that case*/if (!buf || !buf[0]) {if (IS_ENABLED(CONFIG_ACPI_SPCR_TABLE)) {earlycon_init_is_deferred = true;return 0;} else if (!buf) {return early_init_dt_scan_chosen_stdout();}}err = setup_earlycon(buf);if (err == -ENOENT || err == -EALREADY)return 0;return err;}early_param("earlycon", param_setup_earlycon);上⾯的代码会创建⼀个如下结构体,⽤于和cmdline中的参数进⾏匹配:struct obs_kernel_param {const char *str;int (*setup_func)(char *);int early;};然后我们来看关键的setup实现param_setup_earlycon->setup_earlycon:./drivers/tty/serial/earlycon.c:int __init setup_earlycon(char *buf){const struct earlycon_id **p_match;if (!buf || !buf[0])return -EINVAL;if (early_con.flags & CON_ENABLED)return -EALREADY;for (p_match = __earlycon_table; p_match < __earlycon_table_end;p_match++) {const struct earlycon_id *match = *p_match;size_t len = strlen(match->name);if (strncmp(buf, match->name, len))continue;if (buf[len]) {if (buf[len] != ',')continue;buf += len + 1;} elsebuf = NULL;return register_earlycon(buf, match);}return -ENOENT;}最后我们来看关键的setup实现setup_earlycon->register_earlycon:static int __init register_earlycon(char *buf, const struct earlycon_id *match) {int err;struct uart_port *port = &early_console_dev.port;/* On parsing error, pass the options buf to the setup function */if (buf && !parse_options(&early_console_dev, buf))buf = NULL;spin_lock_init(&port->lock);port->uartclk = BASE_BAUD * 16;if (port->mapbase)port->membase = earlycon_map(port->mapbase, 64);earlycon_init(&early_console_dev, match->name);err = match->setup(&early_console_dev, buf);if (err < 0)return err;if (!early_console_dev.con->write)return -ENODEV;register_console(early_console_dev.con);return 0;}最终会调⽤register_console注册printk输出对应的console。
Linux内核:RCU机制与使⽤Linux 内核:RCU机制与使⽤背景学习Linux源码的时候,发现很多熟悉的数据结构多了__rcu后缀,因此了解了⼀下这些内容。
介绍RCU(Read-Copy Update)是数据同步的⼀种⽅式,在当前的Linux内核中发挥着重要的作⽤。
RCU主要针对的数据对象是链表,⽬的是提⾼遍历读取数据的效率,为了达到⽬的使⽤RCU机制读取数据的时候不对链表进⾏耗时的加锁操作。
这样在同⼀时间可以有多个线程同时读取该链表,并且允许⼀个线程对链表进⾏修改(修改的时候,需要加锁)。
RCU适⽤于需要频繁的读取数据,⽽相应修改数据并不多的情景,例如在⽂件系统中,经常需要查找定位⽬录,⽽对⽬录的修改相对来说并不多,这就是RCU发挥作⽤的最佳场景。
RCU(Read-Copy Update),是 Linux 中⽐较重要的⼀种同步机制。
顾名思义就是“读,拷贝更新”,再直⽩点是“随意读,但更新数据的时候,需要先复制⼀份副本,在副本上完成修改,再⼀次性地替换旧数据”。
这是 Linux 内核实现的⼀种针对“读多写少”的共享数据的同步机制。
RCU机制解决了什么在RCU的实现过程中,我们主要解决以下问题:1、在读取过程中,另外⼀个线程删除了⼀个节点。
删除线程可以把这个节点从链表中移除,但它不能直接销毁这个节点,必须等到所有的读取线程读取完成以后,才进⾏销毁操作。
RCU中把这个过程称为宽限期(Grace period)。
2、在读取过程中,另外⼀个线程插⼊了⼀个新节点,⽽读线程读到了这个节点,那么需要保证读到的这个节点是完整的。
这⾥涉及到了发布-订阅机制(Publish-Subscribe Mechanism)。
3、保证读取链表的完整性。
新增或者删除⼀个节点,不⾄于导致遍历⼀个链表从中间断开。
但是RCU并不保证⼀定能读到新增的节点或者不读到要被删除的节点。
RCU(Read-Copy Update),顾名思义就是读-拷贝修改,它是基于其原理命名的。
Linuxkernel驱动相关抽象概念及其实现之“bus,device,driver”bus,device,driver三个很重要的概念贯穿Linux内核驱动架构,特转载⼀篇博⽂:内核的开发者将总线,设备,驱动这三者⽤软件思想抽象了出来,巧妙的建⽴了其间的关系,使之更形象化。
结合前⾯所学的知识,总的来说其三者间的关系为bus有两条链表,分别⽤于挂接设备和驱动,指定了其⾃⾝bus的device或者driver最后都会分别连接到对应bus的这两条链表上,⽽总线⼜有其始端,为bus_kset,⼀个driver可以对应于⼏个设备,因此driver同样有其设备链表,⽤于挂接可以操作的设备,其⾃⾝也有bus挂接点,⽤于将⾃⾝挂接到对应bus(每个driver只属于⼀条总线),⽽对于device,⼀个设备只属于⼀条总线,只能有⼀个driver与其对应,因此对于device,都是单⼀的,⼀个driver挂接点,⼀个bus挂接点,device与bus相同的是都有始端,device为devices_kset,因此device的注册同时会出现在对应的bus⽬录和device总⽬录下。
好了,下⾯就以源码为例分别分析⼀下bus,device,driver的注册过程。
⼀、bus的注册bus的注册⽐较简单,⾸先来看⼀下bus的结构:1struct bus_type {2const char *name; //名字3struct bus_attribute *bus_attrs; //bus属性集4struct device_attribute *dev_attrs; //device属性集5struct driver_attribute *drv_attrs; //driver属性集6int (*match)(struct device *dev, struct device_driver *drv);7int (*uevent)(struct device *dev, struct kobj_uevent_env *env);8int (*probe)(struct device *dev);9int (*remove)(struct device *dev);10void (*shutdown)(struct device *dev);11int (*suspend)(struct device *dev, pm_message_t state);12int (*resume)(struct device *dev);13const struct dev_pm_ops *pm;14struct bus_type_private *p; //bus的私有成员15 };16//其中重点看⼀下私有成员结构体:17struct bus_type_private {18struct kset subsys; //bus内嵌的kset,代表其⾃⾝19struct kset *drivers_kset;20struct kset *devices_kset;21struct klist klist_devices; //包含devices链表及其操作函数22struct klist klist_drivers; //driver链表及其操作函数23struct blocking_notifier_head bus_notifier;24 unsigned int drivers_autoprobe:1; //匹配成功⾃动初始化标志25struct bus_type *bus;26 };⽆论是bus,driver,还是device其本⾝特征都放在私有成员⾥,其注册时,都会申请并填充这个结构体,下⾯具体分析⼀下bus的注册流程,从bus_register开始:1int bus_register(struct bus_type *bus)2 {3int retval;4struct bus_type_private *priv;5 priv = kzalloc(sizeof(struct bus_type_private), GFP_KERNEL); //进⼊时bus_type->bus_type_private为NULL6if (!priv) //该函数主要是对其的设置7return -ENOMEM;8 priv->bus = bus; //私有成员的bus回指该bus9 bus->p = priv; //初始化bus->p,即其私有属性10 BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);11 retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name); //设置该bus的名字,bus是kset的封装12if (retval)13goto out;14//bus_kset即为所有bus的总起始端点15//围绕bus内嵌的kset初始化,和kset的初始化时围绕16 priv->subsys.kobj.kset = bus_kset; //kobj相似,没有parent时,就会⽤kset的kobj,此处即是17 priv->subsys.kobj.ktype = &bus_ktype; //属性操作级别统⼀为bus_ktype18 priv->drivers_autoprobe = 1; //设置该标志,当有driver注册时,会⾃动匹配devices19//上的设备并⽤probe初始化,20//当有device注册时也同样找到 driver并会初始化21 retval = kset_register(&priv->subsys); //注册kset,创建⽬录结构,以及层次关系22if (retval)23goto out;24 retval = bus_create_file(bus, &bus_attr_uevent); //当前bus⽬录下⽣成bus_attr_uevent属性⽂件25if (retval)26goto bus_uevent_fail;27 priv->devices_kset = kset_create_and_add("devices", NULL, //初始化bus⽬录下的devices⽬录,⾥⾯级联了该bus下设备,28 &priv->subsys.kobj); //仍然以kset为原型29if (!priv->devices_kset) {30 retval = -ENOMEM;31goto bus_devices_fail;32 }33 priv->drivers_kset = kset_create_and_add("drivers", NULL, //初始化bus⽬录下的drivers⽬录,⾥⾯级联了该bus下设备的driver34 &priv->subsys.kobj);35if (!priv->drivers_kset) {36 retval = -ENOMEM;37goto bus_drivers_fail;38 }39 klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put); //初始化klist_devices⾥的操作函数成员40 klist_init(&priv->klist_drivers, NULL, NULL); //klist_drivers⾥的操作函数置空41 retval = add_probe_files(bus); //增加bus_attr_drivers_probe和bus_attr_drivers_autoprobe42if (retval) //属性⽂件43goto bus_probe_files_fail;44 retval = bus_add_attrs(bus); //增加默认的属性⽂件45if (retval)46goto bus_attrs_fail;47 pr_debug("bus: '%s': registered/n", bus->name);48return0;49 bus_attrs_fail: //以下为错误处理50 remove_probe_files(bus);51 bus_probe_files_fail:52 kset_unregister(bus->p->drivers_kset);53 bus_drivers_fail:54 kset_unregister(bus->p->devices_kset);55 bus_devices_fail:56 bus_remove_file(bus, &bus_attr_uevent);57 bus_uevent_fail:58 kset_unregister(&bus->p->subsys);59out:60 kfree(bus->p);61 bus->p = NULL;62return retval;63 }由此可见,bus⼜是kset的封装,bus_register主要完成了其私有成员bus_type_private的初始化,并初始化了其下的两个⽬录devices和drivers,及其属性⽂件,bus有个⾃⼰的根⽬录也就是bus有个起始端点,是bus_kset,经过此番的注册,bus⽬录下将会出现我们注册的bus,并且其下会有device和driver两个⼦⽬录,代表它下⾯的driver和device链表。
Linux内核中DMI实现简介Linux内核中DMI实现简介1. 配置在配置内核时,如果选择了CONFIG_DMI选项,会将DMI (Desktop Management interface)功能添加到内核中。
此功能代码在drivers/firmware/Dmi_scan.c文件中。
2. 功能实现2.1 函数调用关系dmi_scan_machineàdmi_presentàdmi_walk_earlyàdmi_table àdmi_decodedmi_scan_machine函数在地址空间0xF0000~0x100000之间通过调用dmi_present函数判断DMI表是否存在;dmi_present函数检查标识_DMI_并计算CRC值来确定DMI表是否正确。
当此表正确时,通过表头信息得到dmi_num、dmi_len、dmi_base值;dmi_walk_early函数通过dmi_num、dmi_len、dmi_base信息,调用dmi_table对DMI表信息进行解析;dmi_table函数调用dmi_decode函数完成DMI信息的解析和存储;dmi_decode解析DMI信息,包含BIOS Information、System Information、Base Board Information、Chassis Information、Onboard Devices Information、OEM Strings、IPMI Device Information、Onboard Devices Extended Information,并且将这些信息存储起来;2.2 DMI信息使用通过dmi_decode函数,已经将相关信息进行了存储。
当内核中其他模块需要使用DMI信息时,可以调用相关函数,比如dmi_check_system。
此函数会调用dmi_matches,如果BIOS中存储的DMI信息与需要查找的信息相匹配,dmi_matches函数会返回真,否则dmi_matches函数会返回假。
Linux 同步方法剖析内核原子,自旋锁和互斥锁你也许接触过并发(concurrency)、临界段(critical section)和锁定,不过怎么在内核中使用这些概念呢?本文讨论了 2.6 版内核中可用的锁定机制,包括原子运算符(atomic operator)、自旋锁(spinlock)、读/写锁(reader/writer lock)和内核信号量(kernel semaphore)。
本文还探讨了每种机制最适合应用到哪些地方,以构建安全高效的内核代码。
本文讨论了 Linux 内核中可用的大量同步或锁定机制。
这些机制为 2.6.23 版内核的许多可用方法提供了应用程式接口(API)。
不过在深入学习 API 之前,首先需要明白将要解决的问题。
并发和锁定当存在并发特性时,必须使用同步方法。
当在同一时间段出现两个或更多进程并且这些进程彼此交互(例如,共享相同的资源)时,就存在并发现象。
在单处理器(uniprocessor,UP)主机上可能发生并发,在这种主机中多个线程共享同一个 CPU 并且抢占(preemption)创建竞态条件。
抢占通过临时中断一个线程以执行另一个线程的方式来实现 CPU 共享。
竞态条件发生在两个或更多线程操纵一个共享数据项时,其结果取决于执行的时间。
在多处理器(MP)计算机中也存在并发,其中每个处理器中共享相同数据的线程同时执行。
注意在 MP 情况下存在真正的并行(parallelism),因为线程是同时执行的。
而在 UP 情形中,并行是通过抢占创建的。
两种模式中实现并发都较为困难。
Linux 内核在两种模式中都支持并发。
内核本身是动态的,而且有许多创建竞态条件的方法。
Linux 内核也支持多处理(multiprocessing),称为对称多处理(SMP)。
临界段概念是为解决竞态条件问题而产生的。
一个临界段是一段不允许多路访问的受保护的代码。
这段代码能操纵共享数据或共享服务(例如硬件外围设备)。
linux系统的内核子系统之间的关系Linux系统的内核子系统之间的关系Linux操作系统的内核是其最核心的组成部分,它负责管理和控制整个系统的运行。
内核由多个子系统组成,每个子系统负责不同的功能模块,它们之间相互配合,共同完成系统的各项任务。
本文将介绍几个常见的内核子系统及其之间的关系。
1. 文件系统子系统文件系统子系统负责管理文件和目录的存储和访问。
它提供了对文件系统的抽象,使用户和应用程序可以通过文件路径来访问文件和目录。
文件系统子系统由虚拟文件系统层、各种具体的文件系统类型和存储设备驱动程序组成。
虚拟文件系统层提供了一个统一的接口,使不同的文件系统可以以相同的方式进行访问。
具体的文件系统类型如ext4、NTFS等负责实现不同的文件系统格式,而存储设备驱动程序则负责控制硬盘、闪存等存储设备的读写。
2. 进程管理子系统进程管理子系统负责管理系统中的进程。
它负责创建、终止和调度进程,并提供进程间通信和同步的机制。
进程管理子系统包括进程调度器、进程控制块、进程间通信和同步机制等。
进程调度器决定了系统中运行哪些进程以及它们的优先级和时间片分配。
进程控制块保存了进程的状态信息,包括程序计数器、寄存器和运行时堆栈等。
进程间通信和同步机制如管道、信号量、消息队列等,使不同进程之间可以进行数据交换和协调工作。
3. 设备驱动子系统设备驱动子系统负责管理和控制硬件设备的访问。
它提供了对设备的抽象接口,使应用程序可以通过统一的方式访问不同类型的设备。
设备驱动子系统包括字符设备驱动和块设备驱动。
字符设备驱动用于管理字符设备,如串口、键盘等,它提供了以字节为单位的读写接口。
块设备驱动用于管理块设备,如硬盘、闪存等,它提供了以块为单位的读写接口。
设备驱动子系统还包括中断处理、DMA控制等功能,用于处理设备的中断请求和数据传输。
4. 网络子系统网络子系统负责管理和控制系统的网络功能。
它提供了网络协议栈、网络接口和网络设备驱动等功能。
linux内核中的workqueue 和work 使用方法示例及解释说明1. 引言1.1 概述Linux内核是操作系统的核心,工作队列(workqueue)和work是其重要的组成部分。
工作队列提供了一种异步机制,用于处理长时间运行的任务或者需要在后台执行的任务。
而work则是具体的任务对象,通过将任务封装为work对象,可以方便地在工作队列中进行调度和管理。
1.2 文章结构本文将详细介绍Linux内核中的工作队列(workqueue)和work的使用方法,并通过示例和解释说明来展示其具体应用。
文章分为五个部分:引言、Workqueue和Work基础知识、Workqueue使用方法、Work使用方法和示例说明以及结论与展望。
1.3 目的本文旨在帮助读者全面了解Linux内核中工作队列和work的概念以及它们的使用方法。
通过深入解析其原理和实践案例,读者可以掌握如何利用工作队列和work来进行高效地后台任务处理,并为未来的研究和应用提供思路和参考。
2. Workqueue和Work基础知识:2.1 Workqueue介绍:Workqueue是Linux内核中的一种机制,用于管理和执行工作任务。
它是一种异步处理的机制,可以在后台处理一些耗时的操作,而不会阻塞系统或其他任务的执行。
2.2 Work介绍:Work是由Workqueue管理的工作任务。
每个Work代表一个需要在后台执行的具体工作。
一个Work可以被认为是一段代码,在特定条件或事件发生时被调用执行。
2.3 Work之间的关系:Workqueue可以创建和管理多个Work任务。
当某个条件满足时,例如硬件中断发生或定时器超时,Workqueue会从任务队列中选择一个可用的Work,并将其分配给空闲的内核线程来运行,以完成相应的工作。
在这个过程中,多个Work之间不存在直接依赖关系。
每个Work都是独立地被分配、执行和管理。
它们并行运行,并且不需要等待其他Work的完成。
Linux 系统⽤户态和内核态Unix/Linux 的体系架构如上图所⽰,从宏观上来看,Linux 操作系统的体系架构分为⽤户态和内核态(或者⽤户空间和内核空间)。
内核从本质上看是⼀种软件-----控制计算机的硬件资源,并提供上层应⽤程序运⾏的环境。
⽤户态即上层应⽤程序的活动空间,应⽤程序的执⾏必须依托于内核提供的资源,包括CPU 资源、存储资源、I/O 资源等。
为了使上层应⽤能够访问到这些资源,内核必须为上层应⽤提供访问的接⼝:。
简单来说::运⾏在内核空间的进程的状态:运⾏在⽤户空间的进程的状态系统调⽤是操作系统的最⼩功能单位,这些系统调⽤根据不同的应⽤场景可以进⾏扩展和裁剪,现在各种版本的Unix 实现都提供了不同数量的系统调⽤,如Linux 的不同版本提供了240-260个系统调⽤,FreeBSD ⼤约提供了320个。
我们可以把系统调⽤看成是⼀种不能再化简的操作(类似于原⼦操作,但是不同概念),有⼈把它⽐作⼀个汉字的⼀个“笔画”,⽽⼀个“汉字”就代表⼀个上层应⽤,我觉得这个⽐喻⾮常贴切。
⼀个汉字有很多笔画组成,因此有时候如果要实现⼀个完整的汉字就必须调⽤很多的系统调⽤。
这有时是⼀件很崩溃的事情,⽐如说这个字,你可能认识,但是有⼏个⼈会写呢?:系统调⽤的封装应⽤程序直接使⽤系统调⽤,这势必会加重程序员的负担,良好的程序设计⽅法是:重视上层的业务逻辑操作,⽽尽可能避免底层复杂的实现细节。
那么有没有优化空间呢?库函数正是为了将程序员从复杂的细节中解脱出来⽽提出的⼀种有效⽅法。
它实现对系统调⽤的封装,将简单的业务逻辑接⼝呈现给⽤户,⽅便⽤户调⽤,从这个⾓度上看,库函数就像是组成汉字的“偏旁”。
这样的⼀种组成⽅式极⼤增强了程序设计的灵活性,对于简单的操作,我们可以直接调⽤来访问资源,如“⼈”;对于复杂操作,我们借助于来实现,如“仁”。
库函数依据不同的标准也可以有不同的实现版本,如ISOC 标准库,POSIX 标准库等。
system design interview vol 1读书笔记在求职过程中,系统设计面试是考察候选人综合能力的重要环节。
本文将为您分享《System Design Interview Vol 1》的读书笔记,帮助您更好地应对系统设计面试。
一、核心概念1.系统设计:指对软件系统的整体架构、组件、模块、接口等进行设计的过程。
2.面向对象设计:将现实世界中的问题抽象为对象,通过对象的属性和方法来描述问题。
3.设计模式:针对特定问题的通用解决方案,可以提高代码的可维护性和可扩展性。
4.性能优化:通过优化算法、数据结构、存储方式等,提高系统的运行效率。
5.可扩展性:指系统在不修改现有代码的情况下,能够方便地添加新功能或模块。
二、关键知识点1.设计原则:- 单一职责原则:一个类或模块只负责一项功能。
- 开放封闭原则:对扩展开放,对修改封闭。
- 里氏替换原则:子类可以替换父类,保证系统稳定性。
- 接口隔离原则:接口尽量细化,避免依赖不必要的接口。
- 依赖倒置原则:高层模块不应该依赖低层模块,两者都应该依赖抽象。
2.设计模式:- 创建型模式:工厂方法、抽象工厂、单例、建造者、原型。
- 结构型模式:适配器、桥接、组合、装饰、外观、享元、代理。
- 行为型模式:责任链、命令、解释器、迭代器、中介者、备忘录、观察者、状态、策略、模板方法、访问者。
3.性能优化:- 数据结构优化:使用高效的数据结构,如哈希表、红黑树等。
- 算法优化:选择合适的算法,减少时间复杂度和空间复杂度。
- 存储优化:合理使用内存、磁盘、缓存等存储方式,提高数据读写效率。
- 网络优化:减少网络请求次数,压缩数据包大小,提高网络传输效率。
4.可扩展性设计:- 模块化设计:将系统划分为独立的模块,便于维护和扩展。
- 接口设计:定义清晰的接口,降低模块间的耦合度。
- 插件化设计:将可变的部分抽象为插件,便于替换和扩展。
三、总结通过阅读《System Design Interview Vol 1》,我们可以了解到系统设计的重要性,掌握设计原则、模式、性能优化和可扩展性设计等关键知识点。
linux操作系统基础、原理与应用 pdf一、引言Linux操作系统是一种功能强大、安全可靠、易于使用的开源操作系统,广泛应用于服务器、超级计算机和移动设备上。
为了帮助读者全面了解Linux操作系统的基本概念、原理和应用,我们编写了这份《Linux操作系统基础、原理与应用pdf》。
本文档将涵盖以下内容:1. Linux基础概念2. Linux操作系统原理3. Linux应用场景和案例分析二、Linux基础概念1. Linux内核:介绍Linux内核的组成、功能和运行机制。
2. 文件系统:讲解Linux中的文件系统和目录结构,包括ext4、Btrfs等常用文件系统。
3. 进程管理:介绍Linux中的进程管理概念,包括进程、线程、僵尸进程等。
4. 系统用户和组:讲解Linux中的用户和组管理,包括用户和组的概念、创建、删除和权限设置等。
5. 设备管理:介绍Linux中的设备管理概念,包括硬件设备驱动、设备文件等。
6. 包管理:讲解Linux中的包管理工具,如APT、yum、dnf等。
7. 系统日志:介绍Linux中的系统日志和日志管理工具,如Syslog、Nagios等。
三、Linux操作系统原理1. Linux进程调度:介绍Linux中的进程调度算法和实现方式。
2. Linux内存管理:讲解Linux中的内存管理机制和原理。
3. Linux文件系统存储:介绍Linux中的文件系统存储机制和RAID技术。
4. Linux网络通信:讲解Linux中的网络通信机制和原理,包括TCP/IP协议栈、路由和DNS解析等。
5. Linux安全机制:介绍Linux中的安全机制和防护措施,如SELinux、防火墙等。
四、Linux应用场景和案例分析1. 服务器运维:介绍如何在服务器上安装和配置Linux,以及如何进行系统管理和维护。
2. 容器技术:讲解Docker和Kubernetes等容器技术的基本概念和使用方法。
linux内核中读写文件数据的方法有时候需要在Linux kernel--大多是在需要调试的驱动程序--中读写文件数据。
在kernel 中操作文件没有标准库可用,需要利用kernel的一些函数,这些函数主要有:filp_open() filp_close(), vfs_read() vfs_write(),set_fs(),get_fs()等,这些函数在linux/fs.h和asm/uaccess.h 头文件中声明。
下面介绍主要步骤1. 打开文件filp_open()在kernel中可以打开文件,其原形如下:strcut file* filp_open(const char* filename, int open_mode, int mode);该函数返回strcut file*结构指针,供后继函数操作使用,该返回值用IS_ERR()来检验其有效性。
参数说明filename:表明要打开或创建文件的名称(包括路径部分)。
在内核中打开的文件时需要注意打开的时机,很容易出现需要打开文件的驱动很早就加载并打开文件,但需要打开的文件所在设备还不有挂载到文件系统中,而导致打开失败。
open_mode:文件的打开方式,其取值与标准库中的open相应参数类似,可以取O_CREAT,O_RDWR,O_RDONLY等。
mode:创建文件时使用,设置创建文件的读写权限,其它情况可以匆略设为02. 读写文件kernel中文件的读写操作可以使用vfs_read()和vfs_write,在使用这两个函数前需要说明一下get_fs()和set_fs()这两个函数。
vfs_read() vfs_write()两函数的原形如下:ssize_t vfs_read(struct file* filp, char __user* buffer, size_t len, loff_t* pos);ssize_t vfs_write(struct file* filp, const char __user* buffer, size_t len, loff_t* pos);注意这两个函数的第二个参数buffer,前面都有__user修饰符,这就要求这两个buffer 指针都应该指向用空的内存,如果对该参数传递kernel空间的指针,这两个函数都会返回失败-EFAULT。
linux内核设计与实现的读书笔记
进程的调度程序是保证进程能有效工作的一个内核子系统。调
度程序负责决定将哪个进程投入运行,何时运行以及运行多少时间。
简单的来说,调度程序就是在给一堆就绪的进程分配处理器的时间,
调度程序是多任务操作系统的基础。调度程序的原则就是最大限度的
使用cpu的资源,也就是说,当系统中只要有可运行的进程,就不能
让cpu处于空闲的状态,如果系统中没有就绪的进程时,则cpu会运
行一个idle进程。
1.多任务
多任务操作系统就是能够同时并发的交互执行多个进程的操作
系统,需要注意这里是并发,而不是并行。如果你的计算机有两个或
者两个以上的cpu那么,你的计算机就可以真正同时、并行的执行多
个任务。多任务操作系统可以分为两类:抢占式多任务和非抢占式多
任务。
抢占式多任务中,由调度程序来决定什么时候停止一个进程的
执行,这种由调度程序强行停止一个进程执行的动作称为抢占
(preemption)。进程在被抢占之前运行的时间是固定的,而且有一个
专门的名字,叫做时间片(timeslice)。时间片实际上是分配给每个
进程的处理器时间段。
而非抢占式多任务是由进程自己做出让步,在执行了一段时间
之后,主动地让出cpu。进程主动挂起自己的操作称为让步(yielding),
如果某个进程悬挂起来并且拒不作出让步的话,可能会导致操作系统
崩溃。
所以总述上面的两种情况,抢占式多任务就像“法律”,只要
时间到了,就把你撤下来。而非抢占式却像“道德”一样,你要是有
道德,执行了一会之后,你就自己撤下来,如果有的“人”占着茅坑
不拉屎,那其他进程除了用“道德”谴责它,也没有其他的办法了。
2.linux进程调度
linux最初的进程调度程序是非常原始的,很难适应一些众多
的可运行进程和多处理器环境。后来从linux2.5开始,对linux的
进程调度程序做了大的调整,使用了称为O(1)的调度算法,这个算
法引起算法行为而得名。O(1)调度算法虽然在数以十计的多处理器上
能表现出近乎完美的特性和可扩展性,但是由于这个算法在调度交互
进程的时候并没有表现出很理想的效果。所以在linux2.6的开发初
期,提出了CFS算法,即完全公平调度算法。
3.策略
(1)IO消耗型进程和处理器消耗型进程
IO消耗型进程指的是进程的大部分时间是用来等待IO的操作,
例如图形用户界面(GUI)程序就属于IO消耗型程序,这个程序需要不
断的监听用户的输入。这样的进程经常处于可运行的状态,但是每次
运行的时间都很短。
处理器消耗型进程是指进程的大部分时间用在执行代码上,比
如大型的计算程序MATLAB就属于处理器消耗型进程。
还有一些应用程序虽然划分为IO消耗型进程,但是也有处理器
消耗型进程的特征。例如,字处理程序,在大多数时间可能等待用户
的输入,但是在某段时间该程序又可能粘住处理器疯狂的进行语法和
拼写错误的检查。
调度程序需要在两个矛盾目标中寻找平衡————进程的迅速
响应和高吞吐量。unix和linux为了获得良好的用户响应,因此都
倾向于调度IO消耗型进程。
(2)进程优先级
调度算法中最基本的一种就是基于进程优先级的调度,这是一
种根据进程的价值和其对处理器的时间需求来对进程分级的一种想
法。通常的做法是优先级高的进程先执行,低的后运行,相同优先级
的进程按轮转方式进行调度(一个接一个,重复进行)。在某些操作系
统中,优先级高的进程的使用的时间片也长一些。调度程序总是选择
优先级高的,并且时间片尚未用尽的进程。
linux系统采用了两种不同类别的优先级,第一种是使用nice
值,范围是从-20到+19,值越大表示优先级越低。这个优先级适用
于一般的进程。
另外,linux对实时进程采用实时优先级,值从0-99,值越大
代表优先级越高。实时进程的优先级都高于普通进程,因此这两个进
程优先级是处于两个互不相交的范围内。
(3)时间片
时间片是一个数值,他表示进程在被抢占前能够持续运行的时
间。时间片过长会导致系统对交互的响应表现欠佳,时间片过短,却
又明显增大进程切换带来的处理器时间消耗。所以IO消耗型进程和
处理器消耗型进程的矛盾在这里又再次显现出来,IO消耗型进程不
需要长的时间片,而处理器消耗型进程则希望时间片越长越好。
长时间片将导致系统的交互性表现欠佳,很多的操作系统都很
重视这一点,因此将时间片设置的很短,如10ms。但是linux的CFS
调度算法并没有直接分配时间片到进程,它是将处理器的使用比分给
了进程,这样进程获得的处理器的时间是和系统负载密切相关的。这
个比例还会受到nice值的影响,nice值作为权重将调整进程使用处
理器时间的使用比。具有更高nice值(低优先级)的进程将被赋予低
权重,从而丧失一小部分处理器的使用比,而具有低nice值(高优先
级)的进程江北赋予高权重,从而获得更多的处理器使用比。
在多数的抢占式操作系统中,一个新就绪的进程能否立即执行
(即抢占原来的进程),完全取决于新进程的优先级和是否有时间片。
而在linux中采用的CFS调度器,其抢占时机取决于新的进程消耗了
多少处理器的使用比,如果新就绪的进程消耗的处理器使用比低于当
前的进程,则新进程抢占当前进程,立即投入运行。