当前位置: 首页 > news >正文

离退休工作网站建设方案tp3.2.3网站开发实例

离退休工作网站建设方案,tp3.2.3网站开发实例,企业建站有什么好处,网络营销工资一般多少嵌入式系统Linux内核开发工程师必须掌握的三十道题 如果你能正确回答以下问题并理解相关知识点原理#xff0c;那么你就可以算得上是基本合格的Linux内核开发工程师#xff0c;试试看#xff01; 1) Linux中主要有哪几种内核锁#xff1f; Linux的内核锁主要是自旋锁和信号… 嵌入式系统Linux内核开发工程师必须掌握的三十道题  如果你能正确回答以下问题并理解相关知识点原理那么你就可以算得上是基本合格的Linux内核开发工程师试试看 1) Linux中主要有哪几种内核锁 Linux的内核锁主要是自旋锁和信号量。 自旋锁最多只能被一个可执行线程持有如果一个执行线程试图请求一个已被争用已经被持有的自旋锁那么这个线程就会一直进行忙循环——旋转——等待锁重新可用。要是锁未被争用请求它的执行线程便能立刻得到它并且继续进行。自旋锁可以在任何时刻防止多于一个的执行线程同时进入临界区。 Linux中的信号量是一种睡眠锁。如果有一个任务试图获得一个已被持有的信号量时信号量会将其推入等待队列然后让其睡眠。这时处理器获得自由去执行其它代码。当持有信号量的进程将信号量释放后在等待队列中的一个任务将被唤醒从而便可以获得这个信号量。 信号量的睡眠特性使得信号量适用于锁会被长时间持有的情况只能在进程上下文中使用因为中断上下文中是不能被调度的另外当代码持有信号量时不可以再持有自旋锁。 2) Linux中的用户模式和内核模式是什么含意 linux中内核本身处于内核模式应用程序处于用户模式。 内核模式的代码可以无限制地访问所有处理器指令集以及全部内存和I/O空间。如果用户模式的进程要享有此特权它必须通过系统调用向设备驱动程序或其他内核模式的代码发出请求。另外用户模式的代码允许发生缺页而内核模式的代码则不允许。 在2.4和更早的内核中仅仅用户模式的进程可以被上下文切换出局由其他进程抢占。除非发生以下两种情况否则内核模式代码可以一直独占CPU (1) 它自愿放弃CPU (2) 发生中断或异常。 2.6内核引入了内核抢占大多数内核模式的代码也可以被抢占。 3) 怎样申请大块内核内存 在Linux内核环境下申请大块内存的成功率随着系统运行时间的增加而减少虽然可以通过vmalloc系列调用申请物理不连续但虚拟地址连续的内存但毕竟其使用效率不高且在32位系统上vmalloc的内存地址空间有限。所以一般的建议是在系统启动阶段申请大块内存但是其成功的概率也只是比较高而已而不是100%。如果程序真的比较在意这个申请的成功与否只能退用“启动内存”Boot Memory。下面就是申请并导出启动内存的一段示例代码  void* x_bootmem Null; EXPORT_SYMBOL(x_bootmem);  unsigned long x_bootmem_size 0; EXPORT_SYMBOL(x_bootmem_size);  static int __init x_bootmem_setup(char *str) { x_bootmem_size memparse(str, str); x_bootmem alloc_bootmem(x_bootmem_size); printk(Reserved %lu bytes from %p for x\n, x_bootmem_size, x_bootmem);  return 1; } __setup(x-bootmem, x_bootmem_setup); 可见其应用还是比较简单的不过利弊总是共生的它不可避免也有其自身的限制 内存申请代码只能连接进内核不能在模块中使用。被申请的内存不会被页分配器和slab分配器所使用和统计也就是说它处于系统的可见内存之外即使在将来的某个地方你释放了它。 一般用户只会申请一大块内存如果需要在其上实现复杂的内存管理则需要自己实现。在不允许内存分配失败的场合通过启动内存预留内存空间将是我们唯一的选择。 4) 用户进程间通信主要哪几种方式 # 管道( pipe )管道是一种半双工的通信方式数据只能单向流动而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。 # 有名管道 (named pipe) 有名管道也是半双工的通信方式但是它允许无亲缘关系进程间的通信。 # 信号量( semophore ) 信号量是一个计数器可以用来控制多个进程对共享资源的访问。它常作为一种锁机制防止某进程正在访问共享资源时其他进程也访问该资源。因此主要作为进程间以及同一进程内不同线程之间的同步手段。 # 消息队列( message queue ) 消息队列是由消息的链表存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。 # 信号 ( sinal ) 信号是一种比较复杂的通信方式用于通知接收进程某个事件已经发生。 # 共享内存( shared memory ) 共享内存就是映射一段能被其他进程所访问的内存这段共享内存由一个进程创建但多个进程都可以访问。共享内存是最快的 IPC 方式它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制如信号两配合使用来实现进程间的同步和通信。 # 套接字( socket ) 套解口也是一种进程间通信机制与其他通信机制不同的是它可用于不同及其间的进程通信。 5) 通过伙伴系统申请内核内存的函数有哪些 6) 通过slab分配器申请内核内存的函数有 7) Linux的内核空间和用户空间是如何划分的以32位系统为例 Linux将4G的地址划分为用户空间和内核空间两部分。在Linux内核的低版本中2.0.X通常0-3G为用户空间3G-4G为内核空间。这个分界点是可以改动的。  正是这个分界点的存在限制了Linux可用的最大内存为2G.而且要通过重编内核调整这个分界点才能达到。实际上还可以有更好的方法来解决这个问题。由于内核空间与用户空间互不重合所以可以用段机制提供的保护功能来保护内核级代码。  2.2.X版的内核对此进行了改动。这样内核空间扩张到了4G。从表面上看内核的基地址变为了0但实际上内核通常仍在虚址3G以上。  用户空间在2.2.X中从直观上变为0-4G让人迷惑不是可以直接访问内核了  其实不然同过使用页机制提供的保护阻止了用户程序访问内核空间。 8) vmalloc()申请的内存有什么特点 9) 用户程序使用malloc()申请到的内存空间在什么范围 KR 的书第8章最后的部分有论述 简单的说就是内存是分散成一些小块的malloc管理的内存是通过链表的 方式把这些块串在一起以这些内存的起始地址排序组织的相邻的内存块如 果尾首地址连续那就把它们合并为一块当你申请一定大小的内存时 以first fit模式,在内存链中找第一个大于你需要大小的内存返回内存指针 以best fit模式要遍历整个内存链找刚好最接近但大于所需要大小的内存 当然这是出于对内存不浪费的考虑效率是有损失.释放的话相反把内存放回 内存管理链中可能的话合并相邻的内存碎片。避免内存过于零散 Linux下malloc函数主要用来在用户空间从heap申请内存申请成功返回指向所分配内存的指针申请失败返回NULL。默认情况下Linux内核使用“乐观的”分配内存策略首先粗略估计系统可使用的内存数然后分配内存但是在使用的时候才真正把这块分配的内存给你。这样一来即使用malloc申请内存没有返回NULL你也不一定能完全使用这块内存特别是在一次或连续多次申请很多内存的时候。 如果一直连续用malloc申请内存而不真正使用所申请的内存总数可以超过真正可以使用的内存数。但是当真正使用这块内存比如用memset或bzero函数一次性把所申请到的大块内存“使用掉”Linux系统就会Out Of Memory这个时候OOM Killer就会kill掉用户空间的其他进程来腾出更多可使用内存。 OOM Killer根据OOM score来决定kill哪个进程OOM score可以看/proc/PID/oom_scorescore由badness函数计算得出根据进程运行时间长短进程优先级进程所使用的内存数等等。可以通过/proc/PID/oom_adj来干预计算socre这个值的取值范围是-1715如果是-17该进程就永远不会被kill这个可能也和内核版本有关不见得所有内核版本都支持得实际试试。 “默认情况”Linux是这种做的“默认情况”是指/proc/sys/vm/overcommit_memory为0的时候。这个参数也可以调整如果为1表示“来着不拒”只要你malloc过来申请我啥都不做立马给你分配内存这样的话性能就会有大幅度的提高如果为2表示Linux会精确计算所有可使用的内存和所申请的内存如果所申请的超过的可使用的内存数就返回NULL。可使用的内存值计算方法虚拟内存swap /proc/sys/vm/overcommit_memory百分比 × 物理内存。/proc/sys/vm/overcommit_memory默认值为50,计算起来就是50%的物理内存数。 Linux自身内核会占一部分内存还有buffer/cache所占用的内存所以实际上能被malloc申请后使用的内存并非物理内存大小demsg的输出里面包含了相关信息如果看不到可能是被别的信息冲掉了重启系统在系统起来后马上看 Memory: 2071220k/2097152k available (2122k kernel code, 24584k reserved, 884k data, 228k init, 1179584k highmem) 10) 在支持并使能MMU的系统中Linux内核和用户程序分别运行在物理地址模式还是虚拟地址模式 11) ARM处理器是通过几级也表进行存储空间映射的 12) Linux是通过什么组件来实现支持多种文件系统的 13) Linux虚拟文件系统的关键数据结构有哪些至少写出四个 14) 对文件或设备的操作函数保存在那个数据结构中 15) Linux中的文件包括哪些 我们在Linux中常用的文件系统主要有ext3、ext2及reiserfs。Linux目前几乎支持所有的Unix类的文件系统除了我们在安装Linux操作系统时所要选择的ext3、reiserfs和ext2外还支持苹果MACOS的HFS也支持其它Unix操作系统的文件系统比如XFS、JFS、Minix fs 及UFS等您可以在kernel的源码中查看如果您想要让系统支持哪些的文件系统得需要把该文件系统编译成模块或置入内核 当然Linux也支持Windows文件系统NTFST和fat但不支持NTFS文件系统的写入支持fat文件系统的读写。现在还有新的ext4文件系统。 16) 创建进程的系统调用有那些 创建进程的调用 启动新进程int system(const char *string)  【includestdlib.h】 替换进程映像int execlint execlvint execlp int execv();execvp();int execve() 【includeunistd.h】 复制进程 fork 17) 调用schedule()进行进程切换的方式有几种 18) Linux调度程序是根据进程的动态优先级还是静态优先级来调度进程的  三、调度策略 1. 进程优先级 (1) 优先级的计算 前面已经说过优先级由两部分构成一是静态优先级static_prio一是动态优先 级prio。静态优先级在进程创建的时候就被赋值并且不变除非用系统调用改变进 程的nice值而进程的动态优先级则是跟static_prio和sleep_avg有关。对于实时 进程的优先级在创建的时候就确定了而且一旦确定以后就不再改变所以下面部分 仅对于非实时进程而言。具体的计算由函数effecitve_prio()kernel/sched.c完 成。 函数将进程的sleep_avg映射成范围是-MAX_BONUS/2 ~ MAX_BONUS/2的变量bonus而 MAX_BONUS是等于 可见sleep_avg仅能影响的优先级范围在-5 ~ 5之间。具体的映 射是由以下规则完成的 那么进程的动态优先级就等于 当然必须在MAX_RT_PRIO和MAX_PRIO-1之间 。可见sleep_avg和bonus是一个线性关系。进程的sleep_avg越大bonus越大 从而进程的动态优先级也就越高。 (2) 何时计算优先级 计算进程的动态优先级一般调用两个函数一个是effective_prio()一个是 recalc_task_prio()。函数recalc_task_prio ()先要根据进程被唤醒前的状态 即actived、interactive_credit等来计算进程的sleep_avg 详见平均等待时间sleep_avg一节在最后调用effective_prio()来计算函数 的动态优先级。总的来说有以下几种情况需要计算进程的优先级 a. 创建新进程使用函数effective_prio()因为此时进程尚未进行调度没有 sleep_avg和interactive_credit可言 b. 唤醒等待进程时使用函数recalc_task_prio ()来计算进程动态优先级。 c. 进程用完时间片以后被重新插入到active array或者expired array的时候需要 重新计算动态优先级以便将进程插入到队列的相应位置。此时使用函数 effective_prio() d. 其他情况如IDLE进程初始化等时候。 2. 进程时间片 (1) 时间片的计算 进程的时间片time_slice是基于进程静态优先级的静态优先级越高值越小时 间片就越大。计算时间片是同过函数task_timeslice()kernel/sched.c来完成的 MAX_BONUS是等于 可见sleep_avg仅能影响的优先级范围在-5 ~ 5之间。具体的映 射是由以下规则完成的 那么进程的动态优先级就等于 当然必须在MAX_RT_PRIO和MAX_PRIO-1之间 。可见sleep_avg和bonus是一个线性关系。进程的sleep_avg越大bonus越大 从而进程的动态优先级也就越高。 (2) 何时计算优先级 计算进程的动态优先级一般调用两个函数一个是effective_prio()一个是 recalc_task_prio()。函数recalc_task_prio ()先要根据进程被唤醒前的状态 即actived、interactive_credit等来计算进程的sleep_avg 详见平均等待时间sleep_avg一节在最后调用effective_prio()来计算函数 的动态优先级。总的来说有以下几种情况需要计算进程的优先级 a. 创建新进程使用函数effective_prio()因为此时进程尚未进行调度没有 sleep_avg和interactive_credit可言 b. 唤醒等待进程时使用函数recalc_task_prio ()来计算进程动态优先级。 c. 进程用完时间片以后被重新插入到active array或者expired array的时候需要 重新计算动态优先级以便将进程插入到队列的相应位置。此时使用函数 effective_prio() d. 其他情况如IDLE进程初始化等时候。 2. 进程时间片 (1) 时间片的计算 进程的时间片time_slice是基于进程静态优先级的静态优先级越高值越小时 间片就越大。计算时间片是同过函数task_timeslice()kernel/sched.c来完成的 。该函数也是使用线性映射的方法将进程优先级[MAX_RT_PRIO, MAX_PRIO-1]映射 到时间片[MIN_TIMESLICE, MAX_TIMESLICE]范围内。通过优先级来计算时间片的等式 为 timeslice MIN_TIMESLICE ((MAX_TIMESLICE - MIN_TIMESLICE) * (MAX_PRIO-1- (p)-static_prio) / (MAX_USER_PRIO-1)) (2) 何时计算时间片 当就绪进程的所有进程的时间片都是0的时候许多操作系统包括旧版本的Linux 是使用下面的循环来给进程队列计算时间片的 for (each task on the system) { recalculate priority; recalculate timeslice } 这样的循环计算会导致以下问题 循环可能会花很长时间而且算法的复杂度O(n) 计算过程中必须给进程队列和task_struct上锁这样可能导致大量的竞争 因为计算时间不可预计所以可能给实时进程带来问题 在Kernel 2.6中时间片的计算是分散的具体的计算既可以用task_timeslice()也 可以用其他方法。 a. 进程创建时将父进程的时间片分一半给子进程同时父进程的时间片减半。 详见sched_fork一节 b. 进程用完时间片以后需要重新计算时间片并将进程插入到相应的运行 队列。详见scheduler_tick一节 c. 进程退出时根据first_timeslice的值来决定是否将子进程的时间片返 还给父进程。详见退出调度一节。 可见Kernel2.6通过分散计算时间片的办法很好解决了上面循环计算所带来的几个问题。 3. 平均等待时间sleep_avg 平均等待时间sleep_avg既决定了进程优先级又影响了进程交互程度的因此它是 Kernel 2.6调度系统里面很复杂的一块。下面将跟踪调度器中sleep_avg的变化情况。 (1) 进程创建 当一个进程被创建的时候父进程的sleep_avg要乘以PARENT_PENALTY / 100子 进程的sleep_avg要乘以CHILD_PENALTY / 100PARENT_PENALTY100而 CHILD_PENALTY 95可见创建以后子进程的sleep_avg要降低而父进程则不变。 (2) 进程被唤醒 当一个进程被唤醒以后acitvate_task()将调用函数recalc_task_prio()来计算进 程的sleep_avg参数是进程的睡眠时间从而进一步计算进程的动态优先级。计算 sleep_avg有以下几种可能当然都需在0 ~ NS_MAX_SLEEP_AVG范围内 a. MAX_SLEEP_AVG - AVG_TIMESLICE 当用户进程p-mm不是由UNINTERRUPTIBLE状态唤醒p-activated ! -1且 睡眠时间大于INTERACTIVE_SLEEP(p)则做此赋值 b. 不变 当用户进程p-mm是由UNINTERRUPTIBLE状态唤醒p-activated -1且 交互程度不高!HIGH_CREDIT(p)如果原来的sleep_avg已经大于INTERACTIVE_SLEEP (p)则不变对非自愿睡眠的进程进行惩罚 否则见下面一条 c. INTERACTIVE_SLEEP(p) 如果加上此次的睡眠时间后大于INTERACTIVE_SLEEP(p)则sleep_avg赋值为 INTERACTIVE_SLEEP(p) d. sleep_avgsleep_time 如果以上条件全都不满足则直接将本次睡眠时间加到sleep_avg上。 (3) 进程调度过程中 在schedule()过程中如果发现优先级最高的程序是刚刚从TASK_INTERRUPTIBLE状态 被唤醒的进程actived0参见actived的定义那么将调用recalc_task_prio ()运算过程与(2)相同所不同的就是调用时的参数sleep_time是进程在就绪队列 的等待时间。如果进程不是被中断唤醒的actived1那么sleep_time还将受到 (ON_RUNQUEUE_WEIGHT * 128 / 100) / 128的限制因为该进程很可能不是交互式 进程。 (4) 进程被剥夺CPU使用权 当进行进程切换的时候被剥夺CPU使用权的进程的sleep_avg将会被减去进程的运行 时间run_time这里的run_time对于交互式进程也有奖励的详见交互式进程优先 一节从而保证调度器的公平性。进程运行的时间越长sleep_avg就越小底限 是0进程的动态优先级也就越低从而被调度器调度到的机会也就会越小。 (5) 进程退出 当一个进程退出时如果该进程的sleep_avg比父进程要小也就是运行时间长 那么父进程将得到惩罚。具体惩罚的规则为 p-parent-sleep_avg p-parent-sleep_avg / (EXIT_WEIGHT1) * EXIT_WEIGHT p-sleep_avg /  (EXIT_WEIGHT 1); 父进程的sleep_avg将变为原来的1/( EXIT_WEIGHT1)再加上子进程的sleep_avg的 1/( EXIT_WEIGHT1)可见子进程运行的越多父进程得到的惩罚也就越大。这样也 是为了保证调度器的公正性。 4. 交互进程优化 Kernel 2.6为了增加系统在高负载情况下的交互感受做了以下三点优化。 (1) interactive_credit -- 奖励sleep_avg interactive_credit是设置在task_struct里面用来标记进程的交互程度的它在 进程创建时候被置为0以后随着不同的情况而增加减少。 增加 interactive_credit有两处增1的地方都在函数recalc_task_prio()里面。 a. 进程所拥有的内存区域不为空(p-mm!NULL)即进程不是内核进程如果不是从 TASK_UNINTERRUPTIBLE状态中被唤醒的p-activated!-1且等待的时间包 括在休眠中等待时间和在就绪队列中等待时间超过了一定限度sleep_time INTERACTIVE_SLEEP(p)此时将interactive_credit增1 b. 进程的等待时间大于NS_MAX_SLEEP_AVG了这种进程很可能是交互进程所以 interactive_credit增1。 减少 interactive_credit只有一处地方减1在函数schedule()里面。当进程将要被切换 出CPU的时候要计算进程的运行时间run_time并将进程的sleep_avg进行调整如 果调整后的sleep_avg小于0说明进程的运行时间大于等待时间而且该进程的 interactive_credit在HIGH_CREDIT(p)和LOW_CREDIT(p)之间说明该进程非交互进程 则将interactive_credit减1作为对进程的惩罚。 从上面的分析可以看出无论interactive_credit如何增减它都在-(CREDIT_LIMIT 1) ~ (CREDIT_LIMIT1)范围内而且当interactive_credit增大到CREDIT_LIMIT 1即调度器认定该进程为交互进程以后interactive_credit就不再变化。 调度器采用宏HIGH_CREDIT()来判断一个进程是否是交互进程如果是则该进程将 得到以下奖励 a. 当进程被剥夺CPU使用权时如果发现该进程是交互进程则将该进程的运行时间 减小run_time / (CURRENT_BONUS(prev) ? : 1)。即sleep_avg减去的运行时间比 实际的运行时间要小从而增加进程的sleep_avg。 b. 交互式进程在就绪队列上等待的时间也将增加到sleep_avg里面p-sleep_avg sleep_time从而增加进程的sleep_avg。 可见对于交互进程都是奖励sleep_avg的从而达到提高优先级的目的。对于交互 式进程调度器并没有在时间片上进行奖励而是在优先级上进行奖励是因为交互 式进程通常是运行时间短、睡眠时间长而且要求响应快而奖励优先级可以给交互 进程更多的运行机会因此调度器对于交互进程的奖励办法是非常公平和科学的。 (2) 平均等待时间sleep_avg -- 奖励动态优先级 在平均等待时间一节已做详细介绍。对于交互进程来说因为它睡眠的时间较长 所以sleep_avg要大一些。另外经常处于TASK_INTERRUPTIBLE状态而且是被中断 唤醒的进程最有可能是交互进程而这种进程的衡量因素也是sleep_avg。 总之由于交互进程一般sleep_avg较大所以调度器通过奖励动态优先级的方式来 使得进程获得更多执行的机会。 (3) TASK_INTERACTIVE() -- 奖励再次被插入active array 这个宏是根据进程的动态优先级和静态优先级来判断该进程的交互程度。在进程时 间片用完时使用这个宏作为一个参考因素来决定是否将进程重新插入active array 。它的定义是 (p)-prio (p)-static_prio - DELTA(p) DELTA(p)      (SCALE(TASK_NICE(p), 40, MAX_BONUS) INTERACTIVE_DELTA) SCALE(v1,v1_max,v2_max) (v1) * (v2_max) / (v1_max) 可以看出这个宏是将进程的动态优先级和进程的静态优先级做比较以判断nice值为 n静态优先级时进程p需要多大的动态优先级才能具有足够的交互性。从宏的 定义可以看出当进程的nice值大于12时进程是不可能被认为是具有足够的交互性 因为nice12时DELTA(p)5而由于sleep_avg给进程带来的动态优先级上的奖励最 大只有5所以TASK_INTERACTIVE(p)永假当进程的nice值为-20时进程的sleep_avg 必须非常小才可能使得TASK_INTERACTIVE(p)值为假。 从以上分析可以看出这三种奖励办法一个比一个奖励力度大奖励条件也一个比一 个苛刻。而且调度器将用户的意愿放在了第一位因为nice值是可以通过系统调用改 变的由于用户的意愿而给予的奖励再次被插入active array最大而调度器 所给予的奖励占的比例并不大。 19) 进程调度的核心数据结构是哪个 1 进程的优先级 每个普通进程都有它自己的静态优先级位于task_struct的static_prio字段调度程序使用静态优先级来估价系统中这个进程与其它普通 进程之间调度强度。但是注意调度程序不是根据静态优先级来决定调度哪个进程的而是动态优先级后面会详细谈到。内核用100最高优先级到 139最低优先级的整数表示普通进程的静态优先级 。注意值越大静态优先级就越低。 新进程总是继承其父进程的静态优先级。不过通过系统调用nice()和setprioritry()用户可以改变自己拥有的进程的静态优先级。 进程静态优先级本质上决定了进程的基本时间片即进程用完了以前的时间片系统分配给进程的时间片长度 。静态优先级和基本时间片的关系用下列公式确定 进程的基本时间片实现函数为task_timeslice static inline unsigned int task_timeslice(struct task_struct *p) { return static_prio_timeslice(p-static_prio); } static unsigned int static_prio_timeslice(int static_prio) { if (static_prio NICE_TO_PRIO(0)) //静态优先级小于120 return SCALE_PRIO(DEF_TIMESLICE * 4, static_prio); //(140-static_prio)*20 else return SCALE_PRIO(DEF_TIMESLICE, static_prio);//(140-static_prio)*5 } #define NICE_TO_PRIO(nice)    (MAX_RT_PRIO (nice) 20) #define MAX_USER_RT_PRIO    100 #define MAX_RT_PRIO        MAX_USER_RT_PRIO 我们看到静态优先级越高其基本时间片就越长。最后的结果是与优先级低的进程相比通常优先级较高的进程获得更长的CPU时间片。 普通进程除了静态优先级还有动态优先级其值的范围也是是100最高优先级MAX_RT_PRIO低于100就成了实时进程了 到139最低优先级MAX_PRIO。动态优先级是调度程序选择新进程来运行的时候使用的数。它与静态优先级的关系用下面的所谓经验公式empirical formula表示 动态优先级 max (100, min (静态优先级 - bonus 5, 139))   (2) 动态优先级的计算主要由 effect_prio() 函数完成该函数实现相当简单从中可见非实时进程的优先级仅决定于静态优先级static_prio和进程的平均睡眠时间sleep_avg两 个因素而实时进程的优先级实际上是在sched_setscheduler() 中设置的详见实时进程调度系统博文以下仅考虑非实时进程且一经设定就不再改变。 动态优先级的计算函数是effective_prio函数effective_prio()读current的static_prio和sleep_avg字段并根据前面的公式计算出进程的动态优先级 static int effective_prio(struct task_struct *p) { p-normal_prio normal_prio(p);//首先计算出普通进程的优先级存放在task_struct的normal_prio字段 if (!rt_prio(p-prio)) return p-normal_prio; return p-prio; //如果是实时进程优先级不变 } static inline int normal_prio(struct task_struct *p) { int prio; if (has_rt_policy(p)) prio MAX_RT_PRIO-1 - p-rt_priority; else prio __normal_prio(p); return prio; } #define rt_prio(prio)        unlikely((prio) MAX_RT_PRIO) //prio小于100就是实时进程 static inline int __normal_prio(struct task_struct *p) {//执行该函数的前提是非实时进程 int bonus, prio; bonus CURRENT_BONUS(p) - MAX_BONUS / 2; prio p-static_prio - bonus; if (prio MAX_RT_PRIO)  // MAX_RT_PRIO的值为100 prio MAX_RT_PRIO;  // 不能让你普通进程的优先级高于实时进程 if (prio MAX_PRIO-1)   // MAX_PRIO的值为140 prio MAX_PRIO-1;   // 不能超过最大优先级139 return prio; } 动态优先级算法的实现关键在 sleep_avg 变量上在effective_prio() 中sleep_avg 的范围是 0~MAX_SLEEP_AVG经过以下公式转换后变成-MAX_BONUS/2~MAX_BONUS/2 之间的 bonus bonus (NS_TO_JIFFIES((p)-sleep_avg) * MAX_BONUS / MAX_SLEEP_AVG) - MAX_BONUS/2 #define MAX_BONUS        (MAX_USER_PRIO * PRIO_BONUS_RATIO / 100) #define MAX_USER_PRIO        (USER_PRIO(MAX_PRIO)) #define USER_PRIO(p)        ((p) - MAX_RT_PRIO) #define MAX_RT_PRIO        MAX_USER_RT_PRIO #define MAX_USER_RT_PRIO    100 .........弄得那么复杂其实MAX_BONUS是定值10MAX_SLEEP_AVG也是定值 #define MAX_SLEEP_AVG        (DEF_TIMESLICE * MAX_BONUS) #define DEF_TIMESLICE        (100 * HZ / 1000) #define CURRENT_BONUS(p) (NS_TO_JIFFIES((p)-sleep_avg) * MAX_BONUS / MAX_SLEEP_AVG) #define NS_TO_JIFFIES(TIME)    ((TIME) / (1000000000 / HZ)) 所以bonus与平均睡眠时间sleep_avg成正比。 不管怎么说sleep_avg 反映了调度系统的两个策略交互式进程优先和分时系统的公平共享。 bonus奖赏是从范围0~10的值值小于5表示降低动态优先级以惩戒值大于5表示增加动态优先级以使奖赏。bonus的值依赖于进程的过去情况与进程的平均睡眠时间有关也就是说平均睡眠时间越久bonus值越大。 那么什么是平均睡眠时间呢粗略地讲平均睡眠时间就是进程在睡眠状态中所消耗的平均纳秒数其存放在task_struck的sleep_avg字段中。注意这绝对不是对过去时间的求平均值操作 因为TASK_INTERRUPTIBLE 状态和TASK_UNINTERRUPTIBLE状态所计算出的平均睡眠时间是不同的而且进程在运行的过程中平均睡眠时间递减。最后平均睡眠时间永远不会大于1s。 根据CURRENT_BONUS宏我们可以得到bonus和sleep_avg的对应关系 平均睡眠时间sleep_avg  bonus  粒度  大于或等于 0 小于 100 ms  0 5120 大于或等于100 小于200 ms  1 2560 大于或等于200 小于300 ms  2 1280 大于或等于300 小于 400 ms  3 640 大于或等于400 小于 500 ms  4 320 大于或等于500 小于 600 ms  5 160 大于或等于600 小于 700 ms  6 80 大于或等于700 小于 800 ms  7 40 大于或等于800 小于 900 ms  8 20 大于或等于900 小于 1000 ms  9 10 1 秒  10 10 平均睡眠时间也被调度程序用来评判一个给定进程是交互式进程还是批处理进程的依据 。如果一个进程满足 动态优先级 ≤ 3 ×  静态优先级/4 28       (3) 那么就看做是交互式进程。 高优先级进程比低优先级进程更容易成为交互式进程。例如具有最高静态优先级100的进程当他的bonus值超过2即睡眠超过200ms时就被看做是交互式进程。判断交互式进程代码的具体实现请参看博文“recalc_task_prio函数 ”。 下面再介绍一些内核调用effective_prio给进程计算优先级的时机计一般在进程状态发生改变内核就有可能计算并设置进程的动态优先级 a) 创建进程 在copy_process()中子进程继承了父进程的动态优先级平分父进程的时间片并添加到父进程所在的就绪队列中。如果父进程不在任何就绪队列 中例如它是 IDLE 进程那么就通过effective_prio() 函数计算出子进程的优先级而后根据计算结果将子进程放置 到相应的就绪队列中。 b) 唤醒休眠进程 核心调用 recalc_task_prio() 设置从休眠状态中醒来的进程的动态优先级再根据优先级放置到相应就绪队列中。 c) 调度到从 TASK_INTERRUPTIBLE 状态中被唤醒的进程 实际上此时调度器已经选定了候选进程但考虑到这一类型的进程很有可能是交互式进程因此此时仍然调用 recalc_task_prio() 对该进程的优先级进行修正修正的结果将在下一次调度时体现。 d) 进程因时间片相关的原因被剥夺 cpu 在 schedule_tick() 中由定时器中断启动进程可能因两种原因被剥夺 cpu一是时间片耗尽一是因时间片过长而分段。这两种情况都会调用effective_prio() 重新计算优先级重新入队。  e) 其它时机 这些其它时机包括IDLE 进程初始化init_idle()、负载平衡以及修改 nice 值set_user_nice()、修改调度策略等主动要求改变优先级的情况。 即使具有较高静态优先级的普通进程获得较大的CPU时间片也不应该使静态优先级较低的进程无法运行。为了避免饥饿当一个进程用完它的时间片时它应该 被还没有用完时间片的低优先级的进程取代。为了实现这种机制调度程序维持两个不相交的可运行进程集合活动进程和过期进程。太复杂了是不别着急我们 还是从数据结构入手。 2 数据结构 回忆一下前面讲的系统中有个0号进程的task_struct结构init_task然后以它打头系统中每个进程的tasks字段链接在一起形成一 个双向循环链表表。另外每个CPU有个运行进程链表runqueue2.6.18内核以后叫做rq存放在位于kernel/Sched.c中称 为运行队列。作为Linux2.6调度程序最重要的数据结构runqueue数据结构存放在runqueues每个CPU变量中宏this_rq() 产生本地CPU运行队列的地址而宏cpu_rq(n)产生索引为n的CPU运行队列地址。 struct runqueue { spinlock_t lock; unsigned long nr_running; #ifdef CONFIG_SMP unsigned long cpu_load; #endif unsigned long long nr_switches; unsigned long nr_uninterruptible; unsigned long expired_timestamp; unsigned long long timestamp_last_tick; task_t *curr, *idle; struct mm_struct *prev_mm; prio_array_t *active, *expired, arrays[2]; int best_expired_prio; atomic_t nr_iowait; #ifdef CONFIG_SMP struct sched_domain *sd; /* For active balancing */ int active_balance; int push_cpu; task_t *migration_thread; struct list_head migration_queue; #endif }; runqueue数据结构中最重要的字段是与可运行进程的链表相关的字段。系统中的每个可运行进程属于且只属于一个运行队列。只要可运行进程保持在同一个运行队列中它就只可能在拥有该运行队列的CPU上执行。 运行队列arrays字段是一个包含两个prio_array_t结构的数组。每个数据结构都表示一个可运行进程的集合并包括140个双向链表头每个链表对应一个可能的进程优先级、一个优先级位图和一个集合中所包含的进程数量的计数器 struct prio_array { unsigned int nr_active; unsigned long bitmap[BITMAP_SIZE]; struct list_head queue[MAX_PRIO]; }; 下图可以看到runqueue结构的active字段指向arrays中的两个prio_array_t数据结构之一对应于包含活动进程的可运行进程 的集合。相反expired字段指向数组中的另一个prio_array_t数据结构对应于包含过去进程的可运行进程的集合。 下面简单说一下rq结构中的其他字段的用处 spinlock_t lockrunqueue 的自旋锁当需要对 runqueue 进行操作时仍然应该锁定但这个锁定操作只影响一个 CPU 上的就绪队列因此竞争发生的概率要小多了。 task_t *curr本 CPU 正在运行的进程。 tast_t *idle指向本 CPU 的 idle 进程表示本地CPU的swapper进程相当于 2.4 中 init_tasks[this_cpu()] 的作用。 int best_expired_prio记录 expired 就绪进程组中的最高优先级数值最小。该变量在进程进入expired 队列的时候保存schedule_tick()用途见下面expired_timestamp的解释。 unsigned long expired_timestamp当新一轮的时间片递减开始后这一变量记录着最早发生的进程耗完时间片事件的时间jiffies 的绝对值在 schedule_tick() 中赋它用来表征expired 中就绪进程的最长等待时间。它的使用体现在 EXPIRED_STARVING(rq)宏上。 上面已经提到每个 CPU 上维护了两个就绪队列active 和 expired。一般情况下时间片结束的进程应该从 active 队列转移到 expired 队列中schedule_tick()但如果该进程是交互式进程实时进程FIFO或RR调度器就会让其保持在active 队列上以提高它的响应速度。这种措施不应该让其他就绪进程等待过长时间也就是说如果 expired 队列中的进程已经等待了足够长时间了即使是交互式进程也应该转移到 expired 队列上来排空 active。这个阀值就体现在EXPIRED_STARVING(rq) 上在 expired_timestamp 和 STARVATION_LIMIT都不等于 0 的前提下如果以下两个条件都满足则 EXPIRED_STARVING() 返回真 ·当前绝对时间 - expired_timestamp STARVATION_LIMIT * 队列中所有就绪进程总数 1也就是说 expired 队列中至少有一个进程已经等待了足够长的时间 ·正在运行的进程的静态优先级比 expired 队列中最高优先级要低best_expired_prio数值要大此时当然应该尽快排空 active 切 换到expired 上来。 struct mm_struct *prev_mm保 存进程切换后被调度下来的进程称之为 prev的 active_mm 结构指针。因为在 2.6 中 prev 的 active_mm 是在进程切换完成之后释放的mmdrop()而此时 prev 的 active_mm 项可能为 NULL所以有必要在runqueue 中预先保留。 unsigned long nr_running本 CPU 上的就绪进程数该数值是 active 和 expired 两个队列中进程数的总和是说明本 CPU 负载情况的重要参数详见调度器相关的负载平衡 。 unsigned long nr_switches记录了本 CPU 上自调度器运行以来发生的进程切换的次数。 unsigned long nr_uninterruptible记录本 CPU 尚处于 TASK_UNINTERRUPTIBLE 状态的进程数和负载信息有关。 atomic_t nr_iowait记录本 CPU 因等待 IO 而处于休眠状态的进程数。 unsigned long timestamp_last_tick本就绪队列最近一次发生调度事件的时间在负载平衡的时候会用到见调度器相关的负载平衡 。 task_t *migration_thread指向本 CPU 的迁移进程。每个 CPU 都有一个核心线程用于执行进程迁移操作见调度器相关的负载平衡 。 struct list_head migration_queue需要进行迁移的进程列表见调度器相关的负载平衡 。 arrays中的两个prio_array_t数据结构的作用会发生周期性的变化活动进程突然变成过期进程而过期进程变化为活动进程调度程序简单地交互运行队列的active和expired字段的内容以完成这种变化。每个进程描述符task_struct都包括几个与调度相关的字段 1) state 进程的状态仍然用 state 表示不同的是2.6 里的状态常量重新定义了以方便位操作 /* 节选自[include/linux/sched.h] */ #define TASK_RUNNING 0 #define TASK_INTERRUPTIBLE 1 #define TASK_UNINTERRUPTIBLE 2 #define TASK_STOPPED 4 #define TASK_ZOMBIE 8 #define TASK_DEAD 16 新增加的TASK_DEAD 指的是已经退出且不需要父进程来回收的进程。 2) timestamp 进程发生调度事件的时间点、时间戳单位是纳秒 —— nanosecond见下。包括以下几类 · 被唤醒的时间在 activate_task() 中设置 · 被切换下来的时间schedule() · 被切换上去的时间schedule() · 负载平衡相关的赋值见调度器相关的负载平衡。 从这个值与当前时间的差值中可以分别获得在就绪队列中等待运行的时长、运行时长等与优先级计算相关的信息见优化了的优先级计算方法。 两种时间单位系统的时间是以 nanosecond十亿分之一秒为单位的但这一数值粒度过细大部分核心应用仅能取得它的绝对值感知不到它的精度。时间相关的核心应用通常围绕 时钟中断进行在 Linux 2.6 中系统时钟每1 毫秒中断一次时钟频率用 HZ 宏表示定义为 1000即每秒中断 1000次这个时间单位称为一个 jiffie。很多核心应用都是以 jiffies 作为时间单位例如进程的运行时间片。 jiffies 与绝对时间之间的转换公式如下 nanosecondjiffies*1000000 核心用两个宏来完成两种时间单位的互换JIFFIES_TO_NS()、NS_TO_JIFFIES()很多时间宏也有两种形式例如 NS_MAX_SLEEP_AVG 和 MAX_SLEEP_AVG。 3) prio 优先级在 0~MAX_PRIO-1 之间取值MAX_PRIO 定义为 140其中 0~MAX_RT_PRIO-1 MAX_RT_PRIO 定义为100属于实时进程范围MAX_RT_PRIO~MX_PRIO-1 属于非实时进程。数值越大表示进程优先级越小。2.6 中动态优先级不再统一在调度器中计算和比较而是独立计算并存储在进程的 task_struct 中再通过上面描述的 priority_array 结构自动排序。 4) static_prio nice 值沿用 Linux 的传统在 -20 到 19 之间变动数值越大进程的优先级越小。nice 是用户可维护的但仅影响非实时进程的优先级。2.6 内核中不再存储 nice 值而代之以 static_priostatic_prio MAX_RT_PRIO nice 20MAX_RT_PRIO100。进程初始时间片的大小仅取决于进程的静态优先级 这一点不论是实时进程还是非实时进程都一样 不过实时进程的 static_prio 不参与优先级计算。  5) activated 表示进程因什么原因进入就绪态这一原因会影响到调度优先级的计算。activated 有四个值 · -1进程从 TASK_UNINTERRUPTIBLE 状态被唤醒 · 0缺省值进程原本就处于就绪态 · 1进程从 TASK_INTERRUPTIBLE 状态被唤醒且不在中断上下文中 · 2进程从 TASK_INTERRUPTIBLE 状态被唤醒且在中断上下文中。 activated 初值为 0在两个地方修改一是在 schedule() 中被恢复为 0另一个就是 activate_task()这个函数由 try_to_wake_up() 函数调用用于激活休眠进程 · 如果是中断服务程序调用的 activate_task()也就是说进程由中断激活则该进程最有可能是交互式的因此置 activated2否则置 activated1。 · 如果进程是从 TASK_UNINTERRUPTIBLE 状态中被唤醒的则activated-1在try_to_wake_up()函数中 。 6) sleep_avg 进程的平均等待时间以 nanosecond 为单位在 0 到 NS_MAX_SLEEP_AVG之间取值初值为 0相当于进程等待时间与运行时间的差值。sleep_avg 所代表的含义比较丰富既可用于评价该进程的交互程度又可用于表示该进程需要运行的紧迫性。这个值是动态优先级计算的关键因子sleep_avg 越大计算出来的进程优先级也越高数值越小。在博文recalc_task_prio函数 中会详细分析 sleep_avg 的变化过程。 7) policy 进程的调度类型SCHED_NORMAL, SCHED_RR, 或 SCHED_FIFO 8) thread_info-flags存放TIF_NEED_RESCHED 标志如果必须调用调度程序则设置该标志 9) thread_info-cpu可运行进程所在运行队列的CPU逻辑号 10) run_list指向进程所属的运行队列链表中的下一个和前一个元素 12) array指向包含进程运行队列的集合prio_array_t 13) last_ran最近一次替换本进程的进程切换时间 14) cpus_allowed能执行进程的CPU的位掩码 15) time_slice在进程的时间片中还剩余的时钟节拍数 16) first_time_slice如果进程肯定不会用完其时间片就把该标志设置为1 17) rt_priority进程的实时优先级 所有state处于TASK_RUNNING状态的进程则在运行队列链表中以run_list组成以prio_array[prio]打头的一个进程循环链表。 当新进程被创建的时候由copy_process()调用的函数sched_fork()用下述方法设置current进程父进程和p进程子进程的time_slice字段 p-time_slice (current-time_slice 1) 1; current-time_slice 1; 由此可以看出父进程剩余的节拍数被划分成两等分一份给父进程另一份给子进程。如果父进程的时间片只剩下一个时钟节拍则划分操作强行把 current-time_slice重新置为1然后调用scheduler_tick()递减该字段从而使 current-time_slice变为0耗尽父进程的时间片把父进程移入expired中。 函数copy_process()也初始化子进程描述符中与进程调度相关的几个字段 p-first_time_slice 1; p-timestamp sched_clock( ); 因为子进程没有用完它的时间片如果一个进程在它的第一个时间片内终止或执行新的程序就把子进程的剩余时间奖励给父进程所以 first_time_slice标志置为1.用函数sched_clock()所产生的时间戳的值初始化timestamp字段函数 sched_clock返回被转化成纳秒的64位寄存器TSC的内容。 3 调度程序所使用的函数 调度程序基本依靠下面几个函数来完成调度工作 scheduler_tick( )维持当前最新的time_slice计数器。 try_to_wake_up( )唤醒睡眠进程。 recalc_task_prio( )更新进程的动态优先级。 schedule( )选择要被执行的新进程。 20) 如何加载、卸载一个模块 一、什么是 modules modules 的字面意思就是模块在此指的是 kernel modules简单来说 一个模块提供了一个功能如 isofs、minix、nfs、lp 等等。传统来讲模块化有两个方法解决 设计者可以把各项功能分离到单独的叫做线程的处理中去 或者是将内核以包含/排除一些功能的方式重新编译。如果把功能分离到线程中去那么内核就叫做“微内核”(micro-kernel)这种解决方法增加了线程间协调工作的通信开销。就象名字暗示的那样这种解决方案的优点在于内核的大小。 linux的解决方案是包含内核模块这些模块是可以按需要随时装入和卸下的。 这样做可以使得内核的大小和通信量都达到最小。将模块从内核中独立出来不必预先『绑』在kernel codes 中。这样做有三种优点 第一 将来修改 kernel 时不必全部重新compile可节省不少时间第二 若需要安装新的 modules 不必重新 compile kernel只要插入(通过insmode指令) 对应的 modules 即可第三减少内核对系统资源的占用 内核可以集中精力做最基本的事情把一些扩展功能都交由modules实现。 模块也可以用来尝试新的内核代码而不需要每次都创建和重激活内核。但是这样做带来的问题是使用内核模块通常会轻微的增加性能和内存开支。一个可加载模块肯定会产生更多的代码这种代码和额外的数据结构会占用更多一点的内存。另外因为间接访问内核资源也让模块的效率轻微降低。 模块化的思想已经被广泛接受主要的原因在于它可以扩展系统的功能用户可以灵活的配置系统。Apache也采取了这种功能扩展方式在本文中主要讨论是内核的模块安装与卸载Apache模块的安装请参照Apapce的相关文档。 二、如何加载模块 加载内核模块的方法有两种。第一种使用insmod命令手工把它插入到内核。另一个更智能的方法是在需要的时候加载这个模块︰这叫做按需加载demand loading。当内核发现需要一个模块的时候例如当用户安装一个不在内核的文件系统的时候内核会请求内核守护进程kerneld试图加载合适的模块。说到这里就不能不提到内核守护进程kerneld了它非常的聪明能够主动的把您需要的modules 自动插入 kernel 将没用到的 module 从kernel中清退。Kerneld由两个独立的部分构成一部分工作于linux的内核负责向daemon发送请求另一部分工作于系统的用户数据区负责调入由内核请求指定的modules。若少了这个kerneld就只能通过手工的方式,用insmode或modeprobe命令进行加载。 三、modules的相关命令介绍 与modules有关的命令有 lsmod : 列出已经被内核调入的模块  insmod : 将某个module插入到内核中  rmmod 将某个module从内核中卸载  modprobe自动根据依赖文件装入模块  depmod : 生成依赖文件告诉modprobe和kerneld要从哪儿调入modules。这个依赖文件就在/lib/modules/kernel版本/modules.dep。  Kerneld负责自动的将模块调入内核和把模块从内核中卸载。  四、编译一个最小的linux内核 模块一般用来支持那些不经常使用的功能。例如通常情况下你仅使用拨号网络因此网络功能并不是任何时候都需要的那么就应该使用可装入的模块来提供这个功能。仅在你进行拨号联接的时候该模块才被装入。而在你断掉连接的时候它会被自动卸下。这样会使内核使用内存的量最小减小系统的负荷。 当然那些象硬盘访问这样时时刻刻都需要的功能则必须作在内核里。如果你搭一台网络工作站或web服务器那么网络功能是时刻都需要的 你就应该考虑把网络功能编译到内核里。另外一个方法是在启动的时候就装入网络模块。这种方法的优点是你不需要重新编译内核。而缺点是网络功能不能特别高效。 按照以上的原则我们首先列出一张清单看看 kernel 中哪些选项是非有不可的也就是说这些东西是必须被编译到内核中的。将那些非必需的模块剔除到内核以外。 第一个是root所在的硬盘配置。如果您的硬盘是IDE接口就把 ide 的选项标记下来。如果是SCSI接口请把您的接口参数及 SCSI id 记标下来。 第二个是选择使用哪一个文件系统。linux的默认文件系统是是 ext2 那么就一定要把它标记下来。如果机器中还其它的操作系统如win98或windows NT您还会可能选择FAT32或NTFS的支持不过后面你可以通过手工加载的方式来加入新的模块支持。 第三个是选择linux所支持的可执行文件格式。这里有两种格式可供选择1、elf这是当前linux普遍支持的可执行文件格式必须编译到内核中 。2、a.out 这是旧版的linux的可执行文件各函数库的格式 如果你确认肯定用不到这种格式的可执行文件那么就可以不把它编译到内核当中。 以上这些内容是必须要编译到内核中的。其它的内容凡是所有选项中m提示的都选择m这样可以通过手工的方式添加该模块。 ** Loadable module support** Enable loadable module support (CONFIG_MODULES) [Y/n/?] Set versioninformation on all symbols for modules (CONFIG_MODVERSIONS) [N/y/?] Kernel daemon support (e.g.autoload of modules) (CONFIG_KERNELD) [Y/n/?] 分别回答 Y,N,Y 。其中 CONFIG_KERNELD 的 default 值是 N 所以要注意选择Y。 make config 完后仍旧是 make dep; make clean。接下来要 make zlilo 或 make zImage。然后 make modules ; make modules_install 。完成之后 就编译出一个没有调入多余模块的一个“干净的”内核映像文件了。 五、如何手工加载Modules 如果要以手工的方式加载模块, 建议最好使用 modprobe, 因为它可以解决模块之间的依赖性问题以声卡的部分来说以sound blaster 为例其总共有以下模块 sb 33652 0 (autoclean) uart401 6160 0 (autoclean) [sb] sound 56492 0 (autoclean) [sb uart401] soundcore 2372 5 (autoclean) [sb sound] 这些模块都要加载上来整个声卡才能工作而且它们之间是有依赖性关系的。最核心的soundcore必须首先装入, 最后装入sb。但一般人是不知道其先后顺序的。因此 modprobe就是用来解决这个问题用的。 通常我们只要modprobe sb它就会自动的找出 sb 用到的所有的模块, 将它们一一的加载进来故一般使用者就不用去伤脑筋了。 那么内核是怎么知道这些模块间的依赖性关系的呢原来在系统启动脚本里有一条depmod -a命令会给系统中的所有可用的模块创建一个依赖关系的列表。而modprobe module-name会使用这个列表在装入指定的模块前先装入那些事先装入的模块。如果在这个从属列表中找不到module-name的话它会给出相应的出错信息。 但若使用 insmod, 它可不会自动完成其它模块的调入。比如说我们要加入PPP模块用这个命令 root/rootinsmod ppp root/root 如果操作成功系统出现操作提示符。如果没有成功可能出现下列信息 /lib/modules/2.2.10/net/ppp.o: unresolved symbol slhc_init_Rsmp_1ca65fca /lib/modules/2.2.10/net/ppp.o: unresolved symbol slhc_compress_Rsmp_cfd3a418 /lib/modules/2.2.10/net/ppp.o: unresolved symbol slhc_free_Rsmp_b99033d9 /lib/modules/2.2.10/net/ppp.o: unresolved symbol slhc_toss_Rsmp_a152cec0 /lib/modules/2.2.10/net/ppp.o: unresolved symbol slhc_remember_Rsmp_07972313 /lib/modules/2.2.10/net/ppp.o: unresolved symbol slhc_uncompress_Rsmp_3bb36b01 [root /root]# 这说明PPP模块没有加载成功错误提示中的unresolved symbol说明 PPP模块所需要的一些模块还没有载入。错误提示第一行的内容是slhc_init_Rsmp_1ca65fca 这是哪个模块这其中可能需要一些经验来做判断它是以slhc开头的就试试slhc吧。 root/rootinsmod slhc 一切正常然后我们再加载PPP模块 root/rootinsmod ppp root/root 这回没有什么返回信息说明PPP模块加载成功了。 六、从内存中卸载一个Modules 要卸载一个模块首先用lsmod看看该模块是否确实已经加载上来然后再做操作。 除此之外在碰到有依赖关系的模块时从内核中卸载模块的过程与载入的过程恰好相反它遵循“first in last out“的准则即在一系列有依赖关系的模块中 必须先卸载最后加载进来的模块最后卸载最先加载进来的模块。比如如果要用 rmmod 移除正在使用中的模块(如上例要卸载slhc, 但仍有PPP模块在使用它)会出现错误提示Device or resource busy 。所以在将PPP模块从内存中卸载后才可能将slhc模块从内存中卸载。 总之在卸载模块时对于可能出现的模块间依赖性问题linux会给你提示足够的信息仔细查看这些信息是能够为你采取相应的操作并最终解决问题提供帮助的。 21) 模块和应用程序分别运行在什么空间 22) Linux中的浮点运算由应用程序实现还是内核实现《内核实现》 23) 模块程序能否使用可链接的库函数 24) TLB中缓存的是什么内容 25) Linux中有哪几种设备 必须先了解Linux所支持的CPU、RAM、显卡等的硬件配备以免造成无法安装。此外同时想、需要考虑即将架设的Linux主机的主要用途。 硬件设备                                               Linux中的代号 IDE 硬盘                                                /dev/hd[a-d] SCSI硬盘       /dev/sd[a-p] 光 驱        /dev/cdrom 软 驱        /dev/fd[0-1] 打印机        /dev/lp[0-2] 鼠 标        /dev/mouse 磁 盘        /dev/ht0(IDE)或/dev/st0 (SCSI界面) 网 卡        /dev/ethn (n由0开始) 26) 字符设备驱动程序的关键数据结构是哪个 27) 设备驱动程序包括哪些功能函数 一、设备驱动程序的作用 驱动程序是应用程序和实际设备之间的一个软件层。为用户提供访问设备的机制而不是提供策略。不带策略的驱动程序典型特征包括同时支持同步和异步操作驱动程序能被多次打开并发使用。 二、内核功能划分 进程管理负责进程的创建和销毁进程间的通信CPU调度 内存管理用来管理内存内核为每个进程创建一个虚拟地址空间 文件系统内核在没有结构的硬件上构造结构化的文件系统支持多文件系统 设备控制也就是驱动程序 网络功能负责在应用程序和网络接口之间传递数据包根据网络活动控制程序的执行。 三、可装载模块 在运行时添加模块linux内核支持好几种模块类型不只是设备驱动程序 四、设备和模块的分类 字符设备字符设备是个能够像字符流一样被访问的设备通常需要实现open close, read,write系统调用大多数设备只能顺序访问。 块设备块设备可以容纳文件系统。进行i/o操作时块设备只能传输一个或多个完成的块每块包含512字节或2的更高次幂的数据。与字符设备相比块设备驱动程序有完全不同的接口。 网络接口网络接口通常是个硬件也可以是个软件由于不是面向流的设备因此将网络接口映射到文件系统中的节点比较困难而是分配一个唯一的名字eth0但这个名字在文件系统中不存在对应的节点。 某些模块时通过某种设备的附加层一起空座(usb, scsi) 文件系统是软件驱动程序将底层数据接口映射成高层数据结构也可以在一个模块中实现不同类型的设备驱动程序 五、安全问题 内核有安全漏洞则整个系统有安全漏洞在正式发行版本中只有授权用户才能装在模块 尽量避免在驱动中实现安全策略最好在系统管理员的控制之下而通常只有特权的用户执行而相关的安全检查必须由驱动程序本身完成。 六、版本编号 偶数为正式发行的稳定版本 技术为开发过程中的一个快照 一、设置测试系统 在2.6内核中构造模块需要系统中中配置并构造好内核树先前的版本只需要有一套内核头文件。 2.6内核的模块要和内核源码树中的目标文件连接可得到一个更加健壮的模块加载器。 二、HelloWord模块 用到的宏 module_init module_exit 分别制定了模块和被加载或卸载时内核调用的函数 MODULE_LICENSE( ) 高速内核采用的自由许可证如果没有模块装载时会产生抱怨 printk 类似c库的printf 在内核中模块不能依赖于c库模块装载后可访问内核的公用符号(包括函数和变量) KERN_ALERT 定义消息的优先级 只是个字符串 如1 消息的显示位置依赖内核版本klogd的版本和配置。 三 核心模块与应用程序的对比 模块退出时必须撤销初始化函数所作的一切 内核中只能调用作为内核一部分的函数大多数相关头文件保存在include/linux和include/asm目录中其他子目录中保存有和特定内核子系统相关的头文件。 调试方式不同和应用程序不同。 四、用户控件和内核空间 内核模块运行在内核空间应用程序运行在用户空间 操作系统的作用是为应用程序提供一个对计算机硬件的一致视图。 操作系统负责程序的独立操作并保护资源不受非法访问。只有cpu能够保护系统软件不受应用程序破坏时不受应用程序破坏时才能完成 在cpu中实现不同的操作模式不同的级别具有不同的功能在较低的级别中禁止某些操作。程序代码只能通过有限数目的门来从一个级别切换到另一个级别。unix系统使用两个级别在x86中使用最高和最低两个级别。 unix中在最高级别也称超级用户态可运行所有操作而应用程序运行在最低级别用户态处理器控制着对硬件的直接访问以及对内存的非授权访问两个级别有自己的内存映射也即自己的地址空间 当应用程序执行系统调用或被硬件中断刮起将切换到内核空间执行系统调用的内核代码运行在进程上下文中因此能够访问进程地址空间的所有数据。处理中断的内核代码和进程是异步的与任何一个特定进程无关。 模块的两类任务 1、模块中某些函数作为系统调用的一部分 2、其它函数负责中断处理 五、内核中的并发 内核代码必须是可重入的。 要时刻考虑并发问题 28) 如何唯一标识一个设备 在linux系统中一切都是文件。所有的硬件设备也都被系统看作是文件而这些硬件设备文件都存放在/dev目录之下但是这种设备文件有时候并不能唯一标识某一个硬件最典型的例子就是那些可移动设备比如U盘之类当系统中接入U盘后可能会将/dev/sda1这个设备名分配给它但是假如这个U盘又插入到别的系统中了那么可能它所分配到的设备名就不是/dev/sda1可能变成了/dev/sdb1。如何让它保持在任何系统中的标识都不变呢当然是有办法的那就是UUID唯一性标识。还是以U盘为例假如有一个U盘分了三个区每个区都会分配有一个UUID这个UUID是记录在U盘上的而不是在某一个系统中这样就不会出现U盘在不同的系统中设备名不同的问题。  下面三个命令可以查看UUID号  1、ls -l /dev/disk/by-uuid/  这个命令可以查看系统中所有具有UUID的设备文件信息  2. vol_id /dev/sdb1  查看/dev/sdb1的卷ID也就是UUID  3. blkid /dev/sdb1  查看块设备/dev/sdb1的UUID像硬盘、U盘、光盘等之类的存储设备都是块设备都可以用这个命令来查看UUID  29) Linux通过什么方式实现系统调用 1.linux系统调用的基本原理 linux的系统调用形式与POSIX兼容也是一套C语言函数名的集合。然而linux系统调用的内部实现方式却与DOC的INT 21H相似它是经过INT 0X80H软中断进入后再根据系统调用号分门别类地服务。 从系统分析的角度linux的系统调用涉及4个方面的问题。 (1)与系统调用有关的数据结构和函数 函数名以“sys_”开头后跟该系统调用的名字。例如系统调用fork()的响应函数是sys_fork()(见Kernel/fork.c),exit()的响应函数是sys_exit()(见kernel/fork.c)。 文件include/asm/unisted.h为每个系统调用规定了唯一的编号。假设用name表示系统调用的名称那么系统调用号与系统调用响应函数的关系是以系统调用号_NR_name作为下标可找出系统调用表sys_call_table(见 arch/i386/kernel/entry.S)中对应表项的内容它正好 是该系统调用的响应函数sys_name的入口地址。系统调用表sys_call_table记录了各sys_name函数在表中的位 置共190项。有了这张表就很容易根据特定系统调用在表中的偏移量找到对应的系统调用响应函数的入口地址。系统调用表共256项余下的项是可供用户自己添加的系统调用空间。 (2)进程的系统调用命令转换为INT 0x80中断的过程 宏定义_syscallN()见include/asm/unisted.h)用于系统调用的格式转换和参数的传递。N取0~5之间的整数。参数个数为N的系统调用由_syscallN()负责格式转换和参数传递。系统调用号放入EAX寄存器启动INT 0x80 后规定返回值送EAX寄存器。 (3)系统调用功能模块的初始化 对系统调用的初始化也就是对INT 0x80的初始化。系统启动时汇编子程序setup_idt(见arch/i386/kernel/head.S)准备了1张256项的idt表由 start_kernel()(见 init/main.c),trap_init()(见 arch/i386/kernel/traps.c)调用的C语言宏定义 set_system_gate(0x80,system_call)(见include/asm/system.h)设置0x80号软中断的服务程序为 system_call(见 arch/i386/kernel/entry.S),system.call就是所有系统调用的总入口。 (4)内核如何为各种系统调用服务 当进程需要进行系统调用时必须以C语言函数的形式写一句系统调用命令。该命令如果已在某个头文件中由相应的_syscallN()展开则用户程序必须包含该文 件。当进程执行到用户程序的系统调用命令时实际上执行了由宏命令_syscallN()展开的函数。系统调用的参数 由各通用寄存器传递然后执行INT 0x80以内核态进入入口地址system_call。 (5)ret_from_sys_call 以ret_from_sys_call入口的汇编程序段在linux进程管理中起到了十分重要的作用。所有系统调用结束前以及大部分中断服务返回前都会跳转至此处入口地址。 该段程序不仅仅为系统调用服务它还处理中断嵌套、CPU调度、信号等事务。 2.通过修改内核源代码添加系统调用 通过以上分析linux系统调用的过程将自己的系统调用加到内核中就是一件容易的事情。下面介绍一个实际的系统调用并把它加到内核中去。要增加的系统调用是inttestsyscall()其功能是在控制终端屏幕上显示hello world执行成功后返回0。 1编写inttestsyscall()系统调用 编写一个系统调用意味着要给内核增加1个函数将新函数放入文件kernel/sys.c中。新函数代码如下 asmlingkage sys_testsyscall() { console_print(hello world\n); return 0; } 2连接新的系统调用 编写了新的系统调用过程后下一项任务是使内核的其余部分知道这一程序的存在然后重建包含新的系统调用的内核。为了把新的函数连接到已有的内核中去 需要编辑2个文件 1).inculde/asm/unistd.h在这个文件中加入 #define_NR_testsyscall 191 2).are/i386/kernel/entry.s这个文件用来对指针数组初始化在这个文件中增加一行 .long SYMBOL_NAME(_sys_tsetsycall) 将.rept NR_syscalls-190改为NR_SYSCALLS-191,然后重新奖励和运行新内核。 3).使用新的系统调用 在保证的C语言库中没有新的系统调用的程序段必须自己建立其代码如下 #inculde _syscall0(int,testsyscall) main() { tsetsyscall(); } 在这里使用了_syscall0()宏指令宏指令本身在程序中将扩展成名为syscall()的函数它在main()函数内部加以调用。在testsyscall()函数中 预处理程序产生所有必要的机器指令代码包括用系统调用参数值加载相应的cpu寄存器 然后执行int 0x80中断指令。 3.利用内核模块添加系统调用 模块是内核的一部分但是并没有被编译到内核里面去。它们被分别编译并连接成一组目标文件 这些文件能被插入到正在运行的内核或者从正在运行的内核中移走。内核模块至少必须有2个函数 int_module和cleanup_module。第一个函数是在把模块插入内核时调用的;第二个函数则在删除该模块时调用。由于内核模块是内核的一部分所以能访问所有内核资源。根据对linux系统调用机制的分析如果要增加系统调用可以编写自己的函数来实现然后在sys_call_table表中增加一项使该项中的指针指向自己编写的函数就可以实现系统调用。下面用该方法实现在控制终端上打印“hello world” 的系统调用testsyscall()。 1)编写系统调用内核模块 #inculde(linux/kernel.h) #inculde(linux/module.h) #inculde(linux/modversions.h) #inculde(linux/sched.h) #inculde(asm/uaccess.h) #define_NR_testsyscall 191 extern viod *sys_calltable[]; asmlinkage int testsyscall() { printf(hello world\n); return 0; } int init_module() { sys_call_table[_NR_tsetsyscall]testsyscall; printf(system call testsyscall() loaded success\n); return 0; } void cleanup_module() { } 2)使用新的系统调用#define #define_NR_testsyscall 191 _syscall0(int,testsyscall) main() { testsyscall(); } 3)编译内核模块并插入内核 编译内核的命令为gcc -Wall -02 -DMODULE -D_KERNEL_-C syscall.c -Wall通知编译程序显示警告信息;参数-02 是关于代码优化的设置 内核模块必须优化;参数-D_LERNEL通知头文件向内核模块提供正确的定义; 参数-D_KERNEL_通知头文件这个程序代码将在内核模式下运行。编译成功后将生成 syscall.0文件。最后使用insmod syscall.o命令将模块插入内核后即可使用增加的系统调用。 比较以上二种方法笔者认为采用内核模块的方法较好。因为这种方法可省去编译新内核并用新内核重新 启动的麻烦这一优点对于代码的调试是非常有价值的 可以节省大量时间。 原文出自【比特网】转载请保留原文链接http://soft.chinabyte.com/os/368/11655868.shtml 30) Linux软中断和工作队列的作用是什么
http://www.ihoyoo.com/news/59014.html

相关文章:

  • 商标注册网站官网做餐饮的餐具网站有哪些
  • 南京做网站南京乐识最优杭州有哪些做网站的公司好
  • 私人做网站的流程学ui设计需要要哪方面基础
  • 大数据比赛网站建设tp框架做视频网站
  • 怎么才能访问自己做的网站海外医疗网站建设
  • 南昌的网站设计做公司网站哪家好 上海
  • 中国交通建设集团第四工程局网站微营销官网
  • 政务网站开发协议下载应用软件排行榜
  • 优质网站有哪些鄞州seo整站优化服务
  • 网站横幅怎么做企业查询湖南
  • 网站建设工作室源码中国加工订单网官网
  • 襄阳seo站内优化室内装饰公司网站模板
  • 做面食视频网站网站图片的暗纹是怎么做的
  • 建筑网站设计方案网站友情链接怎么添加
  • 西安信息网站建设学编程后悔死了
  • 漳州做网站建设的公司百度经验官网首页
  • jquery网站开发实例单位做核酸检测简报
  • 世界500强企业排行榜中国企业宁波网站的优化
  • 网站建设流程详解网站备案需要多少时间
  • 做外贸soho要做网站吗镇江百度推广
  • 建设银行手机网站变淘宝网站开发语言
  • 州网站建设要找嘉艺网络网页版传奇游戏怎么制作
  • 北京网站优化平台wordpress 搭建论坛
  • 网页制作网站素材变身小说 wordpress
  • wordpress 视频不播放西安seo黑
  • 网站对接如何做购物网站排名 2019
  • 购物网站如何做基于vue.js旅游网站开发
  • 天津建站管理系统价格网站备案照片怎么弄
  • 企业网站本身应该就是企业( )的一部分html静态网页制作案例
  • 做博客网站的php代码网络工程是做什么的