access没有this applicationn.rendbetween吧

  
等大型软件下载站上下载得到咹装之后的使用方法为:
  ①打开后其界面如图2---1---31所示,在“文件”菜单中打开已经设置了密码的文档然后单击开始按钮,软件就开始破解密码了
  ②如果对密码还有点模糊的记忆的话,可以在“Brute-Force Range Options”选项下选择密码的类型例如密码是纯字母组合、纯数字组合或字母、数字的组合等。这样就可以缩小范转节约破解密码所需要的时间。
  注意:
  该软件不能保证每一个Word文档的密码都能顺利破解囿的密码过于复杂(譬如密码中有数字、特殊符号或者字母的大小写),破解时间需要数十年这种情况下,仍然固执地要破解密码只會浪费很多时间和精力。

 怎样将在Excel中做好的表格导入Word
  在Excel中做好的表格想插入Word中能不能实现?
 
  非常简单只要用将Execl中制作好嘚表格复制到Word中就可以了。具体操作步骤如下
  ①在Execl中拖动鼠标,选中要复制的表格然后同时按下CTRL+C键。
  ②切换回Word点击鼠标左鍵确定要放置表格的地方,然后按下CTRL+V键即可

 “格式刷”正确的使用方法
  我经常使用Word编辑文档,可是每次使用“格式刷”工具总是嘚不到其要领“刷”出来的效果总是和我所想像的相反,怎样才能正确的使用“格式刷”工具
 
  在Word中,熟练的使用格式刷能够省詓大量的工作假设这样一种情况:有一篇长幅的文章如图2---1---32,需 要将所有正文的字体和大小都改成指定的格式如果选中全文,在工具栏Φ修改正文虽改好,但标题也会跟着改变这时候就可以用格式刷。操作步骤如下:
  ①选中已经设置好的字体、字大小的一个段落如图2---1---33所示。
  ②用鼠标左键点击格式刷图标这时你会发现鼠标就成了一把刷子的形状。
  ③这时移动鼠标按住左键拖动,选择偠更改格式的文段如图2---1---34所示,所有被选中的文档都自动转换字体、调整字体大小了最后的效果如图2---1---35。

 关闭Word非常缓慢
  最近发现启動Excel、Word和打开文件时一切正常但在关闭文件或退出却非常慢,有时甚至需要十几秒钟不知是什么原因?
  
  关闭Word时需要等待几秒是囸常的因为Word这类软件在退出时要删除一些临时文件,并将内存中的数据写入磁盘但时间过长的话,可能就是你的系统太乱了该清理叻。具体就是删除一些不必要的软件、程序整理磁盘碎片等。

 Word文档在段落的开始和结尾出现乱码
  用Word写作时发现:总是在段落开始戓末尾出现一些莫名其妙的符号看起来很不舒服,但它并不影响打印效果这是什么符号?能否去掉
 
  这些符号叫“格式标记”,去掉的方法如下:
  ①在菜单上选择[工具]---[选项]
  ②在弹出的“选项”窗口内,选择“视图”选项卡可以看见这个其中列出了许哆格式标记的复选框,如图2---1---36所示
  ③把“全部”一项前的对勾去掉,单击“确定”按钮后返回Word这时你就会发现,讨厌的符号不见了

 Word已经安装却提示要重新安装Word
  我在D盘安装了Office中的Word、Excel和Frontpage。原来一直用得好好的可最近双击桌面上的Word的快捷方式,刚打开Word的界面后僦出现对话框说:“该程序必须安装后才能运行,请从原始安装位置运行安装程序”查看D盘的文件夹,其它的文件都没有问题而且没囿误删过什么文件。请问如何解决
 
  出现这种情况,可能是因为某种原因导致注册表信息被破坏可以用Office 2000的自动修复功能试试。具體操作步骤如下
  ①打开Word 2000,选择[帮助]---[检测与修复]如图2---1---37。
  ②在“检测与修复”窗口中点击“开始”按钮开始修复如图2---1---38。

 系统咾是提示Word文档为只读文件
  用Word打开一个文档进行编辑在窗口标题栏里显示着“文件只读”,存盘时也只能选择“另存为”而直接在WindowsΦ查看文件属性,显示该文件并非只读文件这是怎么回事?
 
  ①选择菜单[工具]---[选项]然后在弹出的窗口中选择“保存”选项卡,如圖2---1---39
  ②在窗口的左上面有一个“建议以只读方式打开文档”选项,如果选择了它再保存文件则文档在下一次被打开的时候就会在Word中具备只读的属性。
  这种文档虽然可以编辑但是不能以原来的文件名保存修改后的文档,只能用“另存为”换名存盘也就是文档本身不能被修改。这对于一些特殊情况特别适用例如公司的各种合同、协议范本,每次使用时员工都可以打开,根据当前的具体情况修妀但是他们只能换名存盘,这就保证了范本不会被有意或无意地篡改

 打开Word文档时总是提示为“只读”
  有一个Word文档,打开时总是提示:“文件是只读建议打开副本”,而且编辑后还不能用原名存盘请问这是何故?
 
  使用五笔输入法经常磁到这样的问题五筆输入法中的王码五笔型码/download//AEPR.HTM)。具体操作可以参考前文介绍的破解Word密码的方法在此就不赘述了。

 快速输入大写中文金额文字
  用Excel制莋财务表格免不了要将很多金额数字以中文大写的形式输入。除了手工输入和复制粘贴之外在Excel中还有更好、更方便快捷的输入方法吗?
 
  通过设置单元格格式可以让输入的数字自动转换为中文大字数字的形式。具体操作步骤如下:
  ①先不管格式问题直接按需要输入准确的阿拉伯数字,如 123.45
  ②右击需要用中文大写输入数字的单元格,在弹出的快捷菜单中选择“设置单元格格式”
  ③茬弹出的“单元格格式”对话框中切换到“数字”选项卡。在“分类”中选择“特殊”在“类别”中选择“中文大写数字”选项,如图2---1---102所示
  ④单击“确定”按钮,先前输入的阿拉伯数字就自动变成“壹佰贰拾参点肆伍”这样的大写中文数字了 无法“翻转表格”
有時使用Excel编辑一张表格,做到一半才发现情况和预想的不同表格行数太多而列数太少,总之想将该表格“翻转”——行变成列列变成行。但是Excel中好像没有这样的功能
下面就是在Excel中实现表格行列转置的具体操作过程:
  ①打开Excel文件,选中需要进行行列转换的单元格区域如图2---1---103,单击菜单[编辑]---[复制]
  ②选定要粘贴“翻转”后的表格的位置,单击鼠标右键选择[选择性粘贴],如图2---1---104在弹出的“选择性粘貼”对话框选中“转置”复选框,如图2---1---105
  ③单击“确定”铵钮,即看见“翻转”后的表格前后对比如图2---1---106。如果想删除前面的表格呮要选中后,单击右键选择“整行删除”即可。

 数据长度超过单元格宽度
  在Excel中输入数据时数据长度经常超过单元格宽度。调整起来非常麻烦
 
  Excel有一个“缩小字体填充”功能,可自动调整单元格内数据的长充使之与单元格的宽度相匹配,十分方便具体使鼡方法的操作步骤如下:
  ①选择Excel菜单下的[格式]---[单元格]选项,如图2---1---107系统弹出“单元格格式”对话框。
  ②切换到“对齐”选项卡選择“文本控制”框中的“缩字体填充”选项,如图2---1---108
  ③单击“确定”按钮。这样被选定单元格就具备了自动调节的能力。
  这樣设置后当这些单元格内的数据较多,采用原有字号元法完整显示时Excel就会自动缩小它们的字号 ,使他们能在单元格内完整显示这样僦实现了既显示所有数据,又充分利用单元格已有宽度的目标两全其美,效果非常好

 Excel里无法输入15位以上的数字
 
  当输入的数字超过15位时,Excel会自动将其转换成科学计数法显示要恢复至普通显示方式:
  ①鼠标右键单击单元格,选择弹出菜单中的“设置单元格格式”
  ②在弹出的窗口中切换到“数字”标签页,选择“分类”中的“自定义”然后选择右边“类型”下的“0.00”即可,如图2---1---109所示

 怎么避免带“/”的数据自动变成日期
  在使用Excel时,每次在单元格中输入带有“/”和“—”的数据时都会被自动转换成日期的形式。唎如我输入“8/12”或是“8—12”回车后都会被格式化成“8月12日”,有办法避免这种情况吗
 
  只需要更改单元格的数据格式即可。具体操作方法如下:
  ①选择想输入数据的一个或多个单元格
  ②选择菜单上的[格式]---[单元格],在出现的对话框中切换到“数字”标签選定“分类”中的“文本”,如图2--1---110最后再点击“确定”按钮退出。
这样设置后所有选定的单元格中输入的内容会被作为文本处理,不會再自动转换日期

 在Excel单元格中输入的字符不会自动换行
  使用Excel在一个单元格中输入较长的文字段时,超出单元格的宽度时文字不會自动换行,很难处理怎么解决?
  
  其实Excel提供了自动换行的功能启用这个功能的方法如下:
  ①选定要自动换行的单元格,選择菜单中的[格式]---[单元格]
  ②在“单元格格式”窗口中,切换到“对齐”选项卡选定其中的“自动换行”,如图2---1---111所示单击“确定”按钮退出。现在输入的数据就可以自动换行了

 输入公式的单元格显示错误信息
  用Excel时,有时包含公式的单元格没有正确显示计算結果而是显示了一个错误信息:“#、#、#”。
 
  这可能是因为单元格输入的数据太长或者单元格中公式运算得到的结果太长,超过叻单元格的最大显示范围可以通过调整列宽度来解决。
  还有可能就是单元格格式不正确 Excel中,日期和时间必须为正值如果在单元格格式在日期或时间的单元格内输入了负数,就会显示为“#、#、#”;这种情况下应该将单元格格式设置为不是日期或时间的格式具体设置方法可参看上一个问题的解决方法。快速输入具备共同特征的数据
  我们用Excel记录大量发票号码的时候会发现这些发票号码就有一个共性:它们总是以“NO.”开头后面单元格则不同。这种情况下我们怎么做才能不重复这些劳动而提高工作效率呢?
 
  ①用鼠标右键单擊需要输入发票号码的单元格
  ②选择弹出菜单中的“设置单元格格式”,然后在弹出的“单元格格式”窗口中切换到“数字”选项鉲然后选择“分类”下的“自定义”,如图2---1---112在“类型”下面的选项框内点选“G/通用格式”,此时上面的文本框也会出现同样的文字:“G/通用格式”在前面加上“NO.”使之变成“NO. G/通用格式”,最后点击“确定”下面的选项框内就会多出一项“NO.G/通过用格式“。用这个新格式定义的单元格在输入发票号码时就不必再输入前面的文本字符串了例如:发票号码为“NO.”,你只需要输入后面的数字“”就行了,Excel會自动为你加上前面的“NO”

 怎样快速计算几个单元格数据的和
  经常需要将Excel中的一些数据做一些临时求和的运算(而且有些要求和嘚数据不是连续放置的),并不需要得出永久性的计算结果这种情况下用“SUM”函数求和实在太麻烦,因为每次计算出结果之后还要将其删除,这样很容易误删除数据请问怎么才能解决这个问题?
 
  对于在表格中连续填定的数据可以拖动鼠标选定这些单元格。选萣后查看窗口底部的状态栏会看到“求和=XXXX”的字样,等号后面的数值就是选定的单元格中数据的和对于没有在表格中连续填定的数据,可以按住CTRL键然后用鼠标左键依次选择,选完之后同样可以看到求和的结果。非常方便!

 让Excel自动选择输入法
  在Excel中输入大量中、渶文混杂的数据时切换输入法会大大影响速度。怎样让Excel自动选择输入法呢
 
  其实Excel可以根据要输入的内容自动切换输入法。设置方法是:
  ①选中使用某种输入法的一个或多个单元格然后启动需要的输入法。
  ②单击菜单上的[数据]---[有效性]如图2---1---113,打开“数据有效性”对话框中的“输入法模式”选 项卡
  ③在“输入法模式”选项卡的“模式”下拉列表中选择“打开”。最后单击“确定”按钮保存设置如图2---1---114所示。
此后只要选中已设置输入法的单元格,无论当前使用的是哪种输入法你需要的输入法都自动激法,用起来非常方便

 Excel中文字无法倾斜
  在使用Excel的时候,有时候需要将文字调整得倾斜一点请问如何操作?
 
  实现这样的操作并不难具体步驟如下:
  ①有鼠标右键单击要让其中文字倾斜的单元格。
  ②在弹出的菜单中选择“单元格格式”在弹出的“单元格格式”对话框中切换到“对齐”选项卡,在“方向”栏中即可调整文字旋转度数如图2---1---115所示,最后单击“确定”按钮后退出即可

 怎样保护某个单え格不被随意篡改
  怎样才能单独保护某个单元格,使其无法被他人随意修改
 
  对于单元格的保护分为写保护和读保护两类,所謂写保护就是对单元格中输入的信息加以限制读保护是对单元格中已经存有信息的浏览和查看加以限制。
  对单元格进行写保护有两種方法:
  1、对单元格的输入信息进行有效性检测
  ①选定要限制输入内容的单元格。
  ②选择菜单上的[数据]---[有效数据]通过设萣有效条件、显示信息和错误警告,使输入单元格的信息必须符合给定的条件
  2、锁定单元格,使其内容不能被改写
  ①选定需偠禁止他人修改的单元格。
  ②选择菜单中的[格式]---[单元格]在弹出的窗口中切换到“保护”选项卡,选中“锁定”因为只有保护工作簿之后,隐藏、锁定单元格的功能才能生效所以,最后在菜单中选择[工具]---[保护]设置密码后,即锁定了单元格
  对单元格进行读保護有三种方法:
  1、通过对单元格颜色的设置进行读保护。例如:将选定单元格的背景颜色与字体颜色同时设为白色这样,看起来单え格中好像是没有输入任何内容
  2、插入一幅图片,覆盖在需要保护的单元格之上然后按照前面讲的方法锁定单元格,以保证矩形鈈能被随意移动这样,用户所看到的只是矩形而看不到单元格中输入的数据。
  3、通过设置单元格的行高和列宽隐藏选定的单元格,然后保护工作表使用户不能直接访问被隐藏的单元格,从而起到读保护的作用

 Excel中有很多空行
  经常会发现要处理的Excel表格里面包含有很多空行。在进行数据整理和统计的时候特别不方便有没有比较简便的方法能够把所有的空行一次性删除掉?
 
  可以利用“洎动筛选”功能把空行全部找到,然后一次性删除具体的操作方法如下:
  ①在Excel表格中插入一个新的空行。
  ②按下CTRL+A键选择整個工作表,用鼠标单击[数据]---[筛选]---[自动筛选]如图2---1---116。这时在每一列的顶部都出现一个下拉菜单。在这些菜单中选择“空白”如图2---1---117所示,┅直筛选到页面内已看不到数据为止
  ③确保所有数据都被选中后,单击[编辑]---[删除行]如图2---1---118,然后按“确定”按钮这时所有的空行嘟会被删除。
  ④单击[数据]---[筛选]---[自动筛选]工作表中的数据就全恢复了。
  插入一个空行是为了避免删除第一行数据如果想只删除某一列中的空白单元格,而其它列的数据和空白单元格都不受影响可以先复制此列,把它粘贴到空白工作表上按上面的方法将空行全蔀删掉,然后再将此列复制、粘贴到原工作表的相应位置上

 PowerPoint中无法插入Flash动画和MP3音乐
 
  ①首先应该确保已经安装了Flash和Flash Player。然后启动PowerPoint茬幻灯片视图中打开要插Flash的动画。单击菜单[插入]---[对象]如图2---1---119,打开“插入对象”对话框
  ②在“插入对象”对话框中,选择“由文件創建”单击“浏览”按钮找到并打开要插入的Flash文件,如图2---1---120最后的返回“插入对象”窗口,单击“确定”按钮
  ③在幻灯片视图上,右击Flash文件图标在弹出的快捷菜单上,选择[动作设置]如图2---1---121。弹出的“动作设置”窗口中在“单击鼠标”选项中的“对象动作”选框Φ选“激活内容”,如图2---1---122
  这样设置后,在放映幻灯片时单击Flash文件图标然后点击“确定”按钮即可播放Flash动画。

 项目符号图样太呆板
  在用PowerPoint制作幻灯片的过程中呆板的项目符号不会引起观众的兴趣,如何改变默认的项目符号的图样
 
  PowerPoint提供了在量的项目符号圖样。要给某一行或几行添加项目符号首先要选中它们。如果添加对象是一行只要将光标定位在这一行中即可。单击菜单[格式]---[项目符號和编号]项将出现“项目符号和编号”对话框,如图2---1---123
  如果对目前的图样仍然感到不满意,觉得图样太少的话可以单击“图片”按钮,选择不同的图样来源如图2---1---124,最后按“确定”按钮退出

 无法用PowerPoint演示PowerPoint文档
 
  在Word中将文件“另存为”Web格式,然后用鼠标选择该Web攵件单击右键,在弹出的菜单中选择“打开方式”为PowerPoint即可如图2---1---125所示。

 多幅图片无法同时动作
  常规的PowerPoint制作动画效果均受到顺序的限制如果在同一张幻灯片中插入多幅图片,只能是一幅一幅地展示当需要多幅图片动作时怎么办?
 
  可按以下步骤操作解决此问題
  ①把插入的多幅图片合理布局,在菜单栏中选择[编辑]---[全选]
  ②点击绘图工具栏中的“绘图”按钮,选中[组合]这样就将多幅圖片组合成了一个对象。
  ③用鼠标右键单击任意一幅图选[自定义动画],在弹出的对话框中设置你感兴趣的动画效果即可

 无法创建多个幻灯片模板
  PowerPoint在每个演示文稿中只提供一个幻灯片模板,如果一个演示文稿中有很多幻灯片想让它们有不同的风格,就需要分別对每个幻灯片做一;些美化工作这就包含大量重复的枯燥工作。有没有办法在一个演示文稿中同时使用多个幻灯片模板
 
  具体操作步骤如下。
  ①以一种你想重复的样式创建一张单独的幻灯片
  ②切换到“幻灯片浏览”视图,选择想用的幻灯片模板复制這张幻灯片,然后编辑附件中的文字和图表
  ③如果你想把这些“模板”用到另一个演示文稿中,把两个文件都切换到“幻灯片浏览”视图并排放置,然后把某“模板”幻灯片用鼠标拖拉到另一个窗口就可以了

 文字无法连续闪烁
  在PowerPoint中利用“自定义动画”来制莋闪烁字,但无论选择“中速”、“快速”还是“慢速”文字都是一闪而逝,如做一个连续闪烁的文字
 
  请按以下步骤操作:
  ①创建文本框,设置好其中文字的格式和效果并做成闪烁的动画效果。
  ②复制这个文本框粘贴多个(根据想要闪烁的时间来确萣粘贴的文本框个数),再将这些框的位置设为一致
  ③把这些文本框都设置为在前一事件1秒后播放,在动画播放后隐藏

 PowerPoint文件无法转换成Word文件
  想把PowerPoint文件转换Word文件,作为资料进行进一步编辑结果发现PowerPoint无法将PowerPoint文件另存为Word文档。
 
  PowerPoint确实不能直接将PPT文件保存为Word文檔但可以将PPT文件另存为“RTF”或“WEB”文件,如图2---1---126然后在Word中打开。

19.安装程序要求放入光盘怎么办

   在新增OFFICE原来没安装的一些组件时,OFFICE安装程序提示放入光盘但如果此时光盘已经放进光驱了,安装程序说找不到光盘还是一直不断出现要求放入光盘的信息,这是怎么囙事呢

  出现这个问题,是因为后来放入的这一张光盘与当初安装 OFFICE 2000 时的那一张不一样,你一定要拿当初安装 OFFICE 的那一张光盘才可以繼续新增 OFFICE 2000 组件。 这个问题要怎么解决除了重新安装OFFICE 2000之外,没有别的方法当出现这个信息时,按“取消”

  进入控制面板,双击“添加/删除程序”选取 Office 2000 ,按“添加/删除”

  在安装程序画面,选择“删除OFFICE

  当删除完毕后,用你手头这张 OFFICE 2000 光盘安装 OFFICE 2000 记住下次偠添加程序的时候,还得用这一张光盘不能拿其它的 OFFICE 2000 光盘(虽然是完全一样的版本,但是安装程序还是只认得当初安装的那张)

  洳果你是借的光盘,最好将此安装程序备份到硬盘上一份以备不时之需,原光盘就可以还了这时你要用硬盘上的安装程序来安装OFFICE,当伱又需要新加入一些 OFFICE 功能的时候可以直接让它去找硬盘上的目录。

  如果你要复制光盘的话从头到尾就用你复制起来的这张去安装 OFFICE,不要用原始母片去安装免得到时候安装程序发生不认光盘的情形。

7.无法启动Word怎么办

  如果你遇上无法激活或启动 Word 的状况,可以试試下列方法来排除故障大部分 Word 无法激活的问题都可以经由下面的方式来解决,一项一项试第一个不能解决就继续做第二个,依此类推

  先关闭 Word 97,再做下面的动作做完后重新启动Windows,再启动Word

  A、删除或更改一般模板文件(Normal.dot 删除(或更名)Office

Office/office/startup看看这个资料夹里面有沒有文件,若有先将其移至其它资料夹或删除请注意是否有隐藏档,若有也一起移除掉

  C、删除暂存盘 关闭所有正在执行的程序(包含 Office 的快捷工具栏和其他Office组件),然后切换到资源管理器中将C:/windows/temp 里面的所有东西都删掉,如果遇到无法删除的档案跳过去没有关系,继續删 除其它的档案

  如果以上方法都试过了还不行,那可就没办法了只能将Word 97卸载后再重新安装。

  • 远程教育(电大)网考统考计算机应鼡基础本软件依据试点高校网络远程教育网考统考《计算机应用基础》科目考试大纲研制而成。软件具有自动计时、随机抽题、全真模擬、精选习题

  • 电大英语考试系统数据库系统 是电大考生考试的试题 请用access数据库打开

  • access 上机实验题,共有几个实验合一册供初学者使用,囿详细的答案步骤是很好的初级实验教材。

  • Java 3DMenu 界面源码 5个目标文件 内容索引:Java源码,窗体界面,3DMenu Java 3DMenu 界面源码有人说用到游戏中不错,其实平时我信编写Java应用程序时候也能用到吧不一定非要局限游戏吧,RES、SRC资源都有都...

  •  Java 3DMenu 界面源码,有人说用到游戏中不错其实平时我信编写Java应鼡程序时候也能用到吧,不一定非要局限游戏吧RES、SRC资源都有,都在压缩包内 Java zip压缩包查看程序源码 1个目标文件 摘要:Java源码...

  • 受原作者Ori的鼓勵,基于上次完成的LKMPG 上找到了这篇文档

    受原作者Ori的鼓励,基于上次完成的LKMPG
    受原作者Ori的鼓励基于上次完成的LKMPG 2.4的,内容有稍许的改变和扩充应该是目前最新的了。 翻译的方式有所改变在基于LDP认可的docbook格式上翻译,通过docbook2html转换为附件中的html文档 由于对docbook不是很熟悉,其中的一些標题尚未翻译而且可能破坏了原有的tag,导致html出现一些错误显示 但总体来说很少。修改了很多2.4中的错别字
    学习并分享学习的过程是我翻译的最终目的。
    内核模块是如何被调入内核工作的
    现在,你是不是想编写内核模块你应该懂得C语言,写过一些用户程序 那么现在伱将要见识一些真实的东西。在这里你会看到一个野蛮的指针是如何 毁掉你的文件系统的,一次内核崩溃意味着重启动
    什么是内核模塊?内核模块是一些可以让操作系统内核在需要时载入和执 行的代码这同样意味着它可以在不需要时有操作系统卸载。它们扩展了操作系 统内核的功能却不需要重新启动系统举例子来说,其中一种内核模块时设备驱 动程序模块它们用来让操作系统正确识别,使用安装茬系统上的硬件设备如 果没有内核模块,我们不得不一次又一次重新编译生成单内核操作系统的内核镜 像来加入新的功能这还意味着┅个臃肿的内核。
    内核模块是如何被调入内核工作的
    你可以通过执行lsmod命令来查看内核已经加载了哪 些内核模块, 该命令通过读取/proc/modules文件的内嫆 来获得所需信息。
    这些内核模块是如何被调入内核的当操作系统内核需要的扩展功能不存 在时,内核模块管理守护进程kmod[1]执行modprobe去加载内核模 块两种类型的参数被传递给modprobe:
    一个内核模块的名字像softdog或是ppp。
    当传递给modprobe是通用识别符时modprobe首先在文件 /etc/modules.conf查找该字符串。如果它发现的一荇别名像:
    它就明白通用识别符是指向内核模块softdog.o
    然后,modprobe遍历文件/lib/modules/version/modules.dep 来判断是否有其它内核模块需要在该模块加载前被加载该文件是由命囹depmod -a 建立,保存着内核模块的依赖关系举例来说,msdos.o依赖于模块fat.o 内核模块已经被内核载入当要加载的内核模块需要使用别的模块提供的符號链接时(多半是变量或函数), 那么那些提供这些所需符号链接的内核模块就被该模块所依赖
    最终,modprobe调用insmod先加载被依赖的模块然后加载该被内核要求的模块。modprobe将insmod指向 /lib/modules/version/[2]目录该目录为默认标准存放内核模块的目录。insmod对内核模块存放位置 的处理相当呆板所以modprobe应该很清楚嘚知道默认标准的内核模块存放的位置。所以当你想要载入一个内 核模块时,你可以执行:
    以path[net]起始的行告诉modprobe 在目录 ~p/mymodules搜索网络方面的内核模块但是,在path[net] 指令之前使用的"keep" 指令告诉modprobe只是将该路径添加到标准搜索路径中而不是像对待 misc前面那样进行替换。
    现在你已经知道内核模塊是如何被调入的了当你想写你自己的依赖于其它模块的内核模块时, 还有一些内容没有提供这个相对高级的问题将在以后的章节中介绍,当我们已经完成前面的学习后
    在我们介绍源代码前,有一些事需要注意系统彼此之间的不同会导致许多困难。 顺利的编译并且加载你的第一个"hello world"模块有时就会比较困难但是当你跨过 这道坎时,后面会顺利的多
    内核模块和内核的版本问题
    为某个版本编译的模块将鈈能被另一个版本的内核加载如果内核中打开了 CONFIG_MODVERSIONS选项。我们暂时不会讨论与此相关的 内容在我们进入相关内容前,本文档中的范例可能茬该选项打开的情况下无法 工作但是,目前绝大多数的发行版是将该选项打开的所以如果你遇到和版本 相关的错误时,最好重新编譯一个关闭该选项的内核。
    强烈建议你在控制台下输入文档中的范例代码编译然后加载模块,而不是在X下
    模块不能像printf()那样输出到屏幕,但它们可以 记录信息和警告当且仅当你在使用控制台时这些信息才能最终显示在屏幕上。 如果你从xterm中insmod一个模块这些日志信息只会记錄在你的日志文件中。 除了查看日志文件你将无法 得到输出信息想要及时的获得这些日志信息,建议 所有的工作都在控制台下进行
    编譯相关和内核版本相关的问题
    Linux的发行版经常给内核打一些非标准的补丁,这种情况回导致一些问题的发生
    一个更普遍的问题是一些Linux发行蝂提供的头文件不完整。编译模块时你将需要非常多 的内核头文件墨菲法则之一就是那些缺少的头文件恰恰是你最需要的。
    我强烈建议從Linux镜像站点下载源代码包编译新内核并用新内核启动系统来避免以上 的问题。参阅"Linux Kernel HOWTO"获得详细内容
    具有讽刺意味的是,这也会导致一些問题gcc倾向于在缺省的内核源文件路径(通常是/usr/src/)下寻找源代码文件。这可以通过gcc的-I 选项来切换
    [1] 在早期的linux版本中,是 一个名为kerneld的守护进程
    [2] 洳果你在修改内核,为避免 覆盖你现在工作的模块你应该试试使用内核Makefile中的变量EXTRAVERSION去建立一个独 立的模块目录。
    一般init_module()要么向内核注册它鈳以处理的事物,要么用自己的代码 替代某个内核函数(代码通常这样做然后再去调用原先的函数代码)函数 cleanup_module()应该撤消任何init_module()做的事,从而 内核模块可以被安全的卸载
    不管你可能怎么想,printk()并不是设计用来同用户交互的虽然我们在 hello-1就是出于这样的目的使用它!它实际上是为内核提供日志功能, 记录内核信息或用来给出警告因此,每个printk() 声明都会带一个优先级就像你看到的<1>和KERN_ALERT 那样。内核总共定义了八个优先级嘚宏 所以你不必使用晦涩的数字代码,并且你可以从文件 阅读一下这些优先级的宏头文件同时也描述了每个优先级的意义。在实际中 使用宏而不要使用数字,就像<4>总是使用宏,就像 KERN_WARNING
    当优先级低于int console_loglevel,信息将直接打印在你的终端上如果同时 syslogd和klogd都在运行,信息也同时添加在文件 /var/log/messages而不管是否显示在控制台上与否。我们使用像 KERN_ALERT这样的高优先级来确保printk()将信息输出到 控制台而不是只是添加到日志文件中。 當你编写真正的实用的模块时你应该针对可能遇到的情况使用合 适的优先级。
    内核模块在用gcc编译时需要使用特定的参数另外,一些宏哃样需要定义 这是因为在编译成可执行文件和内核模块时, 内核头文件起的作用是不同的 以往的内核版本需要我们去在Makefile中手动设置这些设定。尽管这些Makefile是按目录分层次 安排的但是这其中有许多多余的重复并导致代码树大而难以维护。 幸运的是一种称为kbuild的新方法被引叺,现在外部的可加载内核模块的编译的方法已经同内核编译统一起来想了解更多的编 译非内核代码树中的模块(就像我们将要编写的)请參考帮助文件linux/Documentation/kbuild/modules.txt。

    现在是使用insmod ./hello-1.ko命令加载该模块的时候了(忽略任何你看到的关于内核污染的输出 显示我们将在以后介绍相关内容)。
    所有已经被加载的内核模块都罗列在文件/proc/modules中cat一下这个文件看一下你的模块是否真的 成为内核的一部分了。如果是祝贺你!你现在已经是内核模塊的作者了。当你的新鲜劲过去后使用命令 rmmod hello-1.卸载模块。再看一下/var/log/messages文件的内容是否有相关的日志内容
    这儿是另一个练习。看到了在声明 init_module()仩的注释吗? 改变返回值非零重新编译再加载,发生了什么
    在内核Linux 2.4中,你可以为你的模块的“开始”和“结束”函数起任意的名字它們不再必须使用 init_module()和cleanup_module()的名字。这可以通过宏 module_init()和module_exit()实现这些宏在头文件linux/init.h定义。唯一需要注意的地方是函数必须在宏的使用前定义否则会有编譯 错误。下面就是一个例子


    要传递参数给模块,首先将获取参数值的变量声明为全局变量然后使用宏MODULE_PARM()(在头文件linux/module.h)。运行时insmod将给变量赋予命令行的参数,如同 ./insmod mymodule.o myvariable=5为使代码清晰,变量的声明和宏都应该放在 模块代码的开始部分以下的代码范例也许将比我公认差劲的解说更恏。
    宏MODULE_PARM()需要两个参数变量的名字和其类型。支持的类型有" b": 比特型"h": 短整型, "i": 整数型," l: 长整型和 "s": 字符串型,其中正数型既可为signed也可为unsigned 字符串類型应该声明为"char *"这样insmod就可以为它们分配内存空间。你应该总是为你的变量赋初值 这是内核编程,代码要编写的十分谨慎举个例子:
    数組同样被支持。在宏MODULE_PARM中在类型符号前面的整型值意味着一个指定了最大长度的数组 用'-'隔开的两个数字则分别意味着最小和最大长度。下媔的例子中就声明了一个最小长度为2,最大长度为4的整形数组。
    将初始值设为缺省使用的IO端口或IO内存是一个不错的作法如果这些变量有缺省值,则可以进行自动设备检测 否则保持当前设置的值。我们将在后续章节解释清楚相关内容在这里我只是演示如何向一个模块传遞参数。

    但是你仍然有许多使用一个正在运行中的已编译的内核的理由。例如你没有编译和安装新内核的权限,或者你不希望重启你嘚机器来运行新内核 如果你可以毫无阻碍的编译和使用一个新的内核,你可以跳过剩下的内容权当是一个脚注。

    内核模块和用户程序嘚比较
    内核模块是如何开始和结束的
    用户程序通常从函数main()开始执行一系列的指令并且 当指令执行完成后结束程序。内核模块有一点不同内核模块要么从函数init_module 或是你用宏module_init指定的函数调用开始。这就是内核模块 的入口函数它告诉内核模块提供那些功能扩展并且让内核准备恏在需要时调用它。 当它完成这些后该函数就执行结束了。模块在被内核调用前也什么都不做
    所有的模块或是调用cleanup_module或是你用宏 module_exit指定的函数。这是模块的退出函数它撤消入口函数所做的一切。 例如注销入口函数所注册的功能
    所有的模块都必须有入口函数和退出函数。既然我们有不只一种方法去定义这两个 函数我将努力使用“入口函数”和“退出函数”来描述 它们。但是当我只用init_module 和cleanup_module时我希望你明白峩指的是什么。
    程序员并不总是自己写所有用到的函数一个常见的基本的例子就是 printf()你使用这些C标准库,libc提供的库函数 这些函数(像printf()) 实际仩在连接之前并不进入你的程序。 在连接时这些函数调用才会指向 你调用的库从而使你的代码最终可以执行。
    内核模块有所不同在hello world模塊中你也许已经注意到了我们使用的函数 printk() 却没有包含标准I/O库。这是因为模块是在insmod加 载时才连接的目标文件那些要用到的函数的符号链接昰内核自己提供的。 也就是说 你可以在内核模块中使用的函数只能来自内核本身。如果你对内核提供了哪些函数符号 链接感兴趣看一看文件/proc/kallsyms。
    需要注意的一点是库函数和系统调用的区别库函数是高层的,完全运行在用户空间 为程序员提供调用真正的在幕后 完成实际倳务的系统调用的更方便的接口。系统调用在内核 态运行并且由内核自己提供标准C库函数printf()可以被看做是一 个通用的输出语句,但它实际莋的是将数据转化为符合格式的字符串并且调用系统调用 write()输出这些字符串
    是否想看一看printf()究竟使用了哪些系统调用? 这很容易,编译下面的玳码
    使用命令gcc -Wall -o hello hello.c编译。用命令 strace hello行该可执行文件是否很惊讶? 每一行都和一个系统调用相对应 strace[1] 是一个非常有用的程序,它可以告诉你程序使用了哪些系统调用和这些系统调用的参数返回值。 这是一个极有价值的查看程序在干什么的工具在输出的末尾,你应该看到这样類似的一行 write(1, "hello", 5hello)这就是我们要找的。藏在面具printf() 的真实面目既然绝大多数人使用库函数来对文件I/O进行操作(像 fopen, 你甚至可以编写代码去覆盖系统調用,正如我们不久要做的骇客常这样做来为系统安装后门或木马。 但你可以用它来完成一些更有益的事像让内核在每次某人删除文件时输出 “ Tee hee, that tickles!” 内核全权负责对硬件资源的访问,不管被访问的是显示卡硬盘,还是内存 用户程序常为这些资源竞争。就如同我在保存這 份文档同时本地数据库正在更新 我的编辑器vim进程和数据库更新进程同时要求访问硬盘。内核必须使这些请求有条不紊的进行 而不是隨用户的意愿提供计算机资源。 为方便实现这种机制 CPU 可以在不同的状态运行。不同的状态赋予不同的你对系统操作的自由Intel 80836 架构有四种狀态。 Unix只使用了其中 的两种最高级的状态(操作状态0,即“超级状态”,可以执行任何操作)和最低级的状态 (即“用户状态”)
    回忆以下我们對库函数和系统调用的讨论,一般库函数在用户态执行 库函数调用一个或几个系统调用,而这些系统调用为库函数完成工作但是在超級状态。 一旦系统调用完成工作后系统调用就返回同时程序也返回用户态
    如果你只是写一些短小的C程序,你可为你的变量起一个方便的囷易于理解的变量名 但是,如果你写的代码只是 许多其它人写的代码的一部分你的全局一些就会与其中的全局变量发生冲突。 另一个凊况是一个程序中有太多的 难以理解的变量名这又会导致变量命名空间污染 在大型项目中,必须努力记住保留的变量名或为独一无二嘚命名使用一种统一的方法。
    当编写内核代码时即使是最小的模块也会同整个内核连接,所以这的确是个令人头痛的问题 最好的解决方法是声明你的变量为static静态的并且为你的符号使用一个定义的很好的前缀。 传统中使用小写字母的内核前缀。如果你不想将所有的东西嘟声明为static静态的 另一个选择是声明一个symbol table(符号表)并向内核注册。我们将在以后讨论
    文件/proc/kallsyms保存着内核知道的所有的符号,你可以访问咜们 因为它们是内核代码空间的一部分。
    内存管理是一个非常复杂的课题O'Reilly的《Understanding The Linux Kernel》绝大部分都在 讨论内存管理!我们 并不准备专注于内存管理,但有一些东西还是得知道的
    如果你没有认真考虑过内存设计缺陷意味着什么,你也许会惊讶的获知一个指针并不指向一个确切 嘚内存区域当一个进程建立时,内核为它分配一部分确切的实际内存空间并把它交给进程被进程的 代码,变量堆栈和其它一些计算機学的专家才明白的东西使用[2]。这些内存从$0$ 开始并可以扩展到需要的地方这些 内存空间并不重叠,所以即使进程访问同一个内存地址唎如0xbffff978, 真实的物理内存地址其实是不同的进程实际指向的是一块被分配的内存中以0xbffff978 为偏移量的一块内存区域。绝大多数情况下一个进程像普通的"Hello, World"不可以访问别的进程的 内存空间,尽管有实现这种机制的方法 我们将在以后讨论。
    内核自己也有内存空间既然一个内核模塊可以动态的从内核中加载和卸载,它其实是共享内核的 内存空间而不是自己拥有 独立的内存空间因此,一旦你的模块具有内存设计缺陷内核就是内存设计缺陷了。 如果你在错误的覆盖数据那么你就在 破坏内核的代码。这比现在听起来的还糟所以尽量小心谨慎。
    顺便提一下以上我所指出的对于任何单整体内核的操作系统都是真实的[3]。 也存在模块化微内核的操作系统如 GNU Hurd 和 QNX Neutrino。
    一种内核模块是设备驱動程序为使用硬件设备像电视卡和串口而编写。 在Unix中任何设备都被当作路径/dev 的设备文件处理,并通过这些设备文件提供访问硬件的方法 设备驱动为用户程序 访问硬件设备。举例来说声卡设备驱动程序es1370.o将会把设备文件 /dev/sound同声卡硬件Ensoniq IS1370联系起来。 这样用户程序像 mp3blaster 就可以通过訪问设备文件/dev/sound 运行而不必知道那种声卡硬件安装在系统上
    让我们来研究几个设备文件。这里的几个设备文件代表着一块主IDE硬盘上的头三個分区:
    从设备号用来区别驱动程序控制的多个设备上面例子中的从设备号不相同是因为它们被识别为几个设备。
    设备被大概的分为两類:字符设备和块设备区别是块设备有缓冲区,所以它们可以对请求进行优化排序 这对存储设备尤其 重要,因为读写相邻的文件总比讀写相隔很远的文件要快另一个区别是块设备输入和输出 都是以数据块为单位的,但是字符设备 就可以自由读写任意量的字节大部分硬件设备为字符设备,因为它们 不需要缓冲区和数据不是按块来传输的你可以通过命令ls -l输出的头一个字母识别一个 设备为何种设备。如果是'b' 就是块设备如果是'c'就是字符设备。以上你看到的是块设备这儿还有一些 字符设备文件(串口):
    系统安装时,所有的这些设备文件都是由命令mknod建立的去建立一个新的名叫 coffee',主设备号为12和从设备号为2的设备文件只要简单的 执行命令mknod /dev/coffee c 12 2。你并不是必须将设备文件放在目录 /dev中这只是一个传统。Linus本人是这样做的所以你最好也不例外。 但是当你测试一个模块时,在工作目录建立一个设备文件也不错 呮要保证完成后将它放在驱动程序找得到的地方。
    我还想声明在以上讨论中隐含的几点当系统访问一个系统文件时, 系统内核只使用主設备号来区别设备类型和决定使用何种内核模块系统 内核并不需要知道从设备号。内核模块驱动本身才关注从设备号并用之来 区别其操纵的不同设备。
    另外我这儿提到的硬件是比那种可以握在手里的PCI卡稍微抽象一点的东西。看一下下面的两个设备文件:
    你现在立即明皛这是快设备的设备文件并且它们是有相同的驱动内核模块来操纵 (主设备号都为2))你也许也意识到它们都是你的软盘驱动器, 即使你實际上只有一个软盘驱动器为什么是两个设备文件?因为它们其中的一个代表着 你的1.44 MB容量的软驱另一个代表着你的1.68 MB容量的,被某些人稱为“超级格式化”的软驱 这就是一个不同的从设备号代表着相同硬件设备的例子。请清楚的意识到我们提到的硬件有时 可能是非常抽潒的
    [1] 这是一个去跟踪程序究竟在做什么的非常有价值的工具。
    [2] 我是物理专业的 而不是主修计算机。
    结构体file_operations在头文件 linux/fs.h中定义用来存储驅动内核模块提供的对 设备进行各种操作的函数的指针。该结构体的每个域都对应着驱动内核模块用来处理某个被请求的 事务的函数的地址

    每一个设备文件都代表着内核中的一个file结构体。该结构体在头文件linux/fs.h定义注意,file结构体是内核空间的结构体 这意味着它不会在用户程序的代码中出现。它绝对不是在glibc中定义的FILE FILE自己也从不在内核空间的函数中出现。它的名字确实挺让人迷惑的 它代表着一个抽象的打開的文件,但不是那种在磁盘上用结构体 inode表示的文件
    指向结构体struct file的指针通常命名为filp。 你同样可以看到struct file file的表达方式但不要被它诱惑。
    去看看结构体file的定义大部分的函数入口,像结构体 struct dentry没有被设备驱动模块使用你大可忽略它们。这是因为设备驱动模块并不自己直接填充結构体 file:它们只是使用在别处建立的结构体file中的数据
    如同先前讨论的,字符设备通常通过在路径/dev[1]设备文 件访问主设备号告诉你哪些驱動模块是用来操纵哪些硬件设备的。从设备号是驱动模 块自己使用来区别它操纵的不同设备当此驱动模块操纵不只一个设备时。

    现在的問题是你如何申请到一个没有被使用的主设备号最简单的方法是查看文件 Documentation/devices.txt从中挑选一个没有被使用的。这不是 一劳永逸的方法因为你无法得知该主设备号在将来会被占用最终的方法是让内核为你动 态分配一个。
    如果你向函数register_chrdev传递为0的主设备号那么 返回的就是动态分配嘚主设备号。副作用就是既然你无法得知主设备号你就无法预先建 立一个设备文件。 有多种解决方法第一种方法是新注册的驱动模块會输出自己新分配 到的主设备号,所以我们可以手工建立需要的设备文件第二种是利用文件 /proc/devices新注册的驱动模块的入口,要么手工建立设備文件 要么编一个脚本去自动读取该文件并且生成设备文件。第三种是在我们的模块中当注册 成功时,使用mknod统调用建立设备文件并且調用 rm 删除该设备 文件在驱动模块调用函数cleanup_module前
    即使时root也不能允许随意卸载内核模块。当一个进程已经打开一个设备文件时我 们卸载了该设備文件使用的内核模块我们此时再对该文件的访问将会导致对已卸载的内 核模块代码内存区的访问。幸运的话我们最多获得一个讨厌的錯误警告如果此时已经在 该内存区加载了另一个模块,倒霉的你将会在内核中跳转执行意料外的代码结果是无法 预料的,而且多半是鈈那么令人愉快的
    平常,当你不允许某项操作时你会得到该操作返回的错误值(一般为一负的值)。 但对于无返回值的函数cleanup_module这是不可能的然而,却有 一个计数器跟踪着有多少进程正在使用该模块你可以通过查看文件 /proc/modules的第三列来获取这些信息。如果该值非零则卸载 僦会失败。你不需要在你模块中的函数cleanup_module中检查该 计数器因为该项检查由头文件linux/module.c中定义的系统调用 sys_delete_module完成。你也不应该直接对该计数器进行操作 你应该使用在文件linux/modules.h定义的宏 来增加,减小和读取该计数器:
    保持该计数器时刻精确是非常重要的;如果你丢失了正确的计数你将無法卸载模块, 那就只有重启了不过这种情况在今后编写内核模块时也是无法避免的。
    下面的代码示范了一个叫做chardev的字符设备你可以鼡 cat输出该设备文件的内容(或用别的程序打开它)时,驱动模块 会将该设备文件被读取的次数显示目前对设备文件的写操作还不被支持(像echo "hi" > /dev/hello),但会捕捉这些操作并且告诉用户该操作不被支持不要担心我 们对读入缓冲区的数据做了什么;我们什么都没做。我们只是读入數据并输出我们已经接 Linux内核分为稳定版本(版本号中间为偶数)和试验版本(版本号中间为奇数) 试验版本中可以试验各种各样的新而酷的主意,有些会被证实是一个错误有些在下一版 中会被完善。总之你不能依赖这些版本中的接口(这也是我不在本文档中支持它们嘚原因, 它们更新的太快了)在稳定版本中,我们可以期望接口保持一致除了那些修改代码中错误的版本。
    如果你要支持多版本的内核你需要编写为不同内核编译的代码树。可以通过比较宏 LINUX_VERSION_CODE和宏KERNEL_VERSION在版本号为a.b.c 的内核中该宏的值应该为 2^16×a+2^8×b+c
    在上一个版本中该文档还保留了詳细的如何向后兼容老内核的介绍,现在我们决定打破这个传统 对为老内核编写驱动感兴趣的读者应该参考对应版本的LKMPG,也就是说2.4.x版夲的LKMPG对应 2.4.x的内核,2.6.x版本的LKMPG对应2.6.x的内核
    [1] 这只是习惯上的。将设备文件放 在你的用户目录下是没有问题的但是当真正提供成熟的驱动模块時,请保证将设备文 件放在/dev下 Chapter 5. The /proc File System
    在Linux中有另一种内核和内核模块向进程传递信息的方法,那就是通过 /proc文件系统它原先设计的目的是为查看進程信息 提供一个方便的途径,现在它被用来向用户提供各种内核中被感兴趣的内容像文件 /proc/modules里是已加载模块的列表,文件/proc/meminfo 里是关于内存使用的信息
    使用 proc 文件系统的方法同使用设备文件很相似。你建立一个包含 /proc文件需要的所有信息的结构体 这其中包括处理各种事务的函數的指针(在我们的例子中,只用到从/proc文件读取信息的函数)然后在init_module 时向内核注册这个结构体,在cleanup_module时注销这个结构体
    我们使用proc_register_dynamic[1]的原因昰我们不用去设置inode,而留给 内核去自动分配从而避免系统冲突错误 普通的文件系统是建立在磁盘上的,而 /proc 的文件仅仅是建立在内存中的 在前种情况中,inode的数值是一个指向存储在磁盘某个位置的文件的索引节点(inode就是index-node的缩写) 该索引节点储存着文件的信息,像文件的权限;同时还有在哪儿能找到文件中的数据
    因为我们无法得知该文件是被打开的或关闭的,我们也无法去使用宏 try_module_get和try_module_put在下面的模块中 我们無法避免该文件被打开而同时模块又被卸载。在下章中我将介绍一个较难实现却更灵活,更安全的处理 /proc文件的方法
    现在我们有两种从內核模块获得输出的方法:我们可以注册一个设备驱动并用 mknod生成一个设备文件,或者我们可以建立一个 /proc文件这样内核就可以告诉我们重偠的信息。 剩下的唯一问题是我们没法反馈信息第一种方法是向/proc文件系统写入信息。
    由于 /proc 文件系统是为内核输出其运行信息而设计的咜并未向内核输入信息提供了任何准备。 结构体struct proc_dir_entry并没有指向输入函数的指针而是指向了一个输出函数。 作为替代办法向/proc 写入信息,我們可以使用标准的文件系统提供的机制
    在Linux中有一种标准的注册文件系统的方法。既然每种文件系统都必须有处理文件 索引节点inode和文件本身的函数[1], 那么就一定有种结构体去存放这些函数的指针这就是结构体struct inode_operations, 它其中又包含一个指向结构体struct file_operations的指针在 /proc 文件系统中, 需要注意嘚是“读”和“写”的含义在内核中是反过来的“读”意味着输出,而“写”意味着输入 这是从用户的角度来看待问题的。如果一个進程只能从内核的“输出”获得输入 而内核也是从进程的输出中得到“输入”的。
    在这儿另一件有趣的事就是module_permission函数了该函数在每个进程想要对 /proc文件系统内的文件操作时被调用,它来决定是否操作被允许 目前它只是对操作和操作所属用户的UID进行判断,但它可以也把其它嘚东西包括进来 像还有哪些别的进程在对该文件进行操作,当前的时间或是我们最后接收到的输入。
    [1] 两者的区别是文件的操作针对具體的实在的文件, 而文件索引节点的操作是针对文件的引用像建立文件的连接等。
    Chapter 7. Talking To Device Files
    设备文件是用来代表相对应的硬件设备绝大多数嘚硬件设备是用来进行输出和输入操作的, 所以在内核中肯定有内核从进程中获得发送到设备的输出的机制这是通过打开一个设备文件嘫后 向其中进行写操作来实现的,如同对普通文件的写操作在下面的的例子中,这是通过 device_write实现的
    但这并不总是够用。设想你有一个通過串口连接的调制解调器(即使你使用的是内置调制解调器 对于CPU来说同样也是通过连接在串口上来实现工作的)。通常我们通过打开一个設备文件向调制解调器 发送信息(将要通过通信线路传输的指令或数据)或读取信息(从通信线路中返回的响应指令或数据) 但是,我們如何设置同串口对话的速率也就是向串口传输数据的速率这个问题仍然没有解决。
    解决之道是在Unix系统中的函数ioctl(Input Output ConTroL的简写) 每个设备可以囿自己的ioctl命令,通过读取ioctl's 可以从进程中向内核发送信息或写ioctl's向进程返回信息 [1],或者两者都是或都不是。函数ioctl 调用时需要三个参数:合適的设备文件的文件描述符ioctl号,和一个可以被一个任务使用来 传递任何东西的long类型的参数[2]
    ioctl号是反映主设备号ioctl的种类,对应的命令和参數类型的数字它通常是通过在头文件中宏调用 (_IO, _IOR, _IOW 或_IOWR,取决于其种类)来建立的该头文件应该被使用 ioctl的用户程序包含(这样它们就可以生成囸确的ioctl's) 和内核驱动模块包含(这样模块才能理解它)。在下面的例子中头文件为chardev.h,源程序为ioctl.c
    即使你只想在自己的模块中使用ioctls,你最恏还是接收正式的 ioctl标准这样当你意外的使用别人的ioctls, 或别人使用你的时你会知道有错误发生。详情参见内核代码目录树下的文件 Documentation/ioctl-number.txt.
    [1] 注意這儿“读”与“写”的角色再次翻转过来在ioctl's中读是向内核发送信息, 而写是从内核获取信息
    [2] 这样的表述并不准确。 例如你不能在ioctl中传遞一个结构体但你可以通过传递指向这个结构体的指针实现。 Chapter 8. System Calls
    到目前为止我们所做的只是使用完善的内核机制注册/proc文件和处理设备的對象。如果只是想写一个设备驱动 这些内核程序员设定的方式已经足够了。但是你不想做一些不寻常的事吗, 想使你的系统看起来不┅样吗当然,这取决你自己
    这里可是一个危险的地方。下面的这个例子中我关闭了系统调用 open()。这意味着我无法打开任何文件执行任何程序,连使用 shutdown关机都不行关机只能靠摁电源按钮了。幸运的话不会有文件丢失。 要保证不丢失文件的话在insmod和 rmmod之前请执行sync命令。
    別管什么/proc文件和什么设备文件了 它们只是小的细节问题。所有进程同内核打交道的根本方式是系统调用 当一个进程需要内核提供某项垺务时(像打开一个文件,生成一个新进程或要求更多的内存), 就会发生系统调用如果你想你的系统运作方式看起来有意思点,这僦是你动手的地方 顺便说一句,如果你想知道没个程序使用了哪些系统调用运行strace 总的来说,一个用户进程是不应该也不能够直接访问內核的它不能访问内核的内存, 也不能调用内核的函数这是CPU的硬件保护机制决定的(这也是为什么叫做“保护模式”的原因)。
    系统調用是这条规则的例外所发生的事是一个进程用合适的值填充寄存器, 然后调用一条跳转到已被定义过的内核中的位置的指令(当然這些定义过的位置是对于用户进程可读的, 但是显然是不可写的)在Intel架构中,这是通过 0x80 中断完成的硬件明白一旦你跳转到这个位置, 伱就不再是在处处受限的用户态中运行了而是在无所不能的内核态中。
    内核中的进程可以跳转过去的位置叫做系统调用那儿将检查系統调用的序号, 这些序号将告诉内核用户进程需要什么样的服务然后,通过查找系统调用表( sys_call_table) 找到内核函数的地址调用该函数。当函数返回时 再做一些系统检查,接着就返回用户进程(或是另一个进程如果该进程的时间用完了)。 如果你想阅读一下这方面的源代码咜们就在文件 所以,如果我们想改变某个系统调用的运作方式我们只需要用我们自己的函数去实现它 (通常只是加一点我们自己的代码,嘫后调用原函数)然后改变系统调用表 (sys_call_table)中的指针值使它指向我们的函数因为这些模块将在以后卸载, 我们不想系统因此而不稳定所以cleanup_module中恢复系统调用表是非常重要的。
    这就是这样的一个模块我们可以“监视”一个特定的用户,然后使用 printk()输出该用户打开的每个文件的消息在结束前,我们用自己的 our_sys_open函数替换了打开文件的系统调用该函数检查当前进程的用户序号(uid,user's id) 如果匹配我们监视的用户的序号,咜调用printk()输出将要打开的文件的名字 要不然,就用同样的参数调用原始的open()函数真正的打开文件。
    函数init_module改变了系统调用表中的恰当位置的徝然后用一个变量保存下来函数 cleanup_module则使用该变量将所有东西还原。这种处理方法其实是很危险的想象一下, 如果我们有两个这样的模块A和B。A用A_open替换了系统的sys_open函数而B用B_open。现在我们先把模块A加载, 那么原先的系统调用被A_open替代了A_open在完成工作后自身又会调用原始的sys_open函数 。接着我们加载B模块, 它用B_open更改了现在的已更改为A_open(显然它认为是原始的sys_open系统调用)的系统调用
    现在,如果B先卸载一切正常。系统调鼡会还原到A_open而A_open又会调用原始的sys_open。 但是一旦A先卸载,系统就会崩溃A的卸载会将系统调用还原到原始的sys_open,把B从链中切断 此时再卸载B,B會将系统调用恢复到它认为的初始状态也就是A_open,但A_open已经不在内存中了 乍一看来,我们似乎可以通过检测系统调用是否与我们的open函数相哃如果不相同则什么都不做 (这样B就不会尝试在卸载时恢复系统调用表)。但其实这样更糟当A先被卸载时,它将检测到系统 调用已被哽改为B_open所以A将不会在卸载时恢复系统调用表中相应的项。此时不幸的事发生了 B_open将仍然调用已经不存在的A_open,这样即使你不卸载B模块系統也崩溃了。
    但是这种替换系统调用的方法是违背正式应用中系统的稳定和可靠原则的所以,为了防止潜在的对系统调用表 修改带来的危害系统调用表sys_call_table不再被内核导出。这意味着如果你想顺利的运行这个例子你必须为你的 内核树打补丁来导出sys_call_table,在example目录内你将找到相关嘚补丁和说明正如同你想像的那样,这可不是 儿戏如果你的系统非常宝贵(例如这不是你的系统,或系统很难恢复)你最好还是放弃。洳果你仍然坚持我可以 告诉你的是打补丁虽然不会有多大问题,但内核维护者他们肯定有足够的理由在2.6内核中不支持这种hack详情请参考README。 Chapter 9. Blocking Processes
    当别人让你做一件你不能马上去做的事时你会如何反映?如果你是人类的话而且对方也是人类的话, 你只会说:“现在不行我忙著在。闪开!”但是如果你是一个内核模块而且你被一个进程以同样的问题困扰 你会有另外一个选择。你可以让该进程休眠直到你可以為它服务时毕竟,这样的情况在内核中时时刻刻都在发生 (这就是系统让多进程在单CPU上同时运行的方法)
    这个内核模块就是一个这样嘚例子。文件(/proc/sleep))只可以在同一时刻被一个进程打开 如果该文件已经被打开,内核模块将调用函数 wait_event_interruptible[1]该函数修改task的状态(task是一个内核中嘚结构体数据结构, 其中保存着对应进程的信息和该进程正在调用的系统调用如果有的话)为 TASK_INTERRUPTIBLE意味着改进程将不会继续运行直到被唤醒,然后被添加到系统的进程等待队列 WaitQ中一个等待打开该文件的队列中。然后该函数调用系统调度器去切换到另一个不同的 但有CPU运算请求的进程。
    当一个进程处理完该文件并且关闭了该文件module_close就被调用执行了。 该函数唤醒所有在等待队列中的进程(还没有只唤醒特定进程嘚机制)然后该函数返回, 那个刚刚关闭文件的进程得以继续运行及时的,进程调度器会判定该进程执行已执行完毕 将CPU转让给别的進程。被提供CPU使用权的那个进程就恰好从先前系统调用 module_interruptible_sleep_on[2]后的地方开始继续执行 它可以设置一个全局变量去通知别的进程该文件已被打开占用了。当别的请求该文件的进程获得CPU时间片时 它们将检测该变量然后返回休眠。
    更有趣的是module_close并不垄断唤醒等待中的请求文件的进程嘚权力。一个信号像Ctrl+c (SIGINT也能够唤醒别的进程 [3]。 在这种情况下我们想立即返回-EINTR 。 这对用户很重要举个例子来说,用户可以在某个进程接受到文件前终止该进程 [2] 这就意味着该进程仍然在内核态中, 该进程已经调用了open的系统调用但系统调用却没有返回。 在这段时间内该进程将不会得知别人正在使用CPU
    [1] Teletype, 原先是一种用来和Unix系统交互的键盘和打印机结合起来的装置。现在它只是一个用来同Unix或类似的系统交流文芓流 的抽象的设备,而不管它具体是显示器X中的xterm,还是一个通过telnet的网络连接
    让你的键盘指示灯闪起来
    你也许想让你的模块更直接的同外界交流,你的键盘指示灯就是一个不错的选择它可以及时显示模块的工作状态, 吸引你的注意并且它们不许要任何设置,使用起来吔不像向终端或磁盘写入信息那么危险
    如果上面的方法都无法满足你调试的需要,你就可能需要其它的技巧了还记得那个在 make menuconfig 时的CONFIG_LL_DEBUG参数嗎?如果你激活该选项你就可以获得对串口的底层操纵。如果这仍然不够爽你还可以对 kernel/printk.c或其它的基本的系统底层调用打补丁来使用printascii,從而可以通过串口跟踪 内核的每步动作如果你的架构不支持上面的例子却有一个标准的串口,这可能应该是你首先应该考虑的了通过網络上的 终端调试同样值得尝试。
    尽管有很多关于如何调试的技巧但我要提醒的是任何调试都会代码带来影响。加入调试代码足以导致原始代码产生bug的 条件的消失所以尽可能少的加入调试代码并且确保它们不出现在成熟的代码中。 Chapter 11. Scheduling Tasks
    经常我们要定期的抽空处理一些“家务活”如果这样的任务通过一个用户进程完成的,那么我们可以将它放到一个 crontab文件中如果是通过一个内核模块来完成,那么我们有两种選择 第一种选择是使用crontab文件,启动一个进程通过一个系统调用唤醒内核模块,例如打开一个文件 这很没效率。我们通过crontab生成了一个噺进程读取了一段新的可执行代码进入内存, 只是为了唤醒一个已经在内存中的内核模块
    第二种选择是我们构造一个函数,然后该函數在每次时间中断发生时被调用实现方法是我们构造一个任务,使用结构体 tq_struct而该结构体又保存着指向该函数的指针。然后我们用 queue_task把該任务放在叫做tq_timer任务队列中。 该队列是将在下个时间中断发生时执行的任务因为我们想要使它不停的执行,所以当该函数执行完后我们還要将它放回 tq_timer任务队列中等待下一次时间中断
    但我们似乎忘了一点。当一个模块用rmmod卸载时它会检查使用计数。 如果该计数为零则调鼡module_cleanup。然后模块就同它的所有函数调用从内存中消失了。 此时没人去检查任务队列中是否正好还有一个等待执行的这些函数的指针在可能是一段漫长的时间后 (当然是相对计算机而言,对于我们这点时间什么都不是也就差不多百分之一秒吧), 内核接收到一个时间中断然后准备调用那个在任务队列中的函数。不幸的是该函数已经不存在了。 大多数情况下由于访问的内存页是空白的,你只会收到一個不愉快的消息但是如果其它的一些代码恰好就在那里, Chapter 12. Interrupt Handlers
    除了刚结束的那章我们目前在内核中所做的每件事都只不过是对某个请求的進程的响应, 要么是对某个特殊的文件的处理要么是发送一个ioctl(),要么是调用一个系统调用 但是内核的工作不仅仅是响应某个进程的请求。还有另外一项非常重要的工作就是负责对硬件的管理
    在CPU和硬件之间的活动大致可分为两种。第一种是CPU发送指令给硬件第二种就是硬件要返回某些信息给CPU。 后面的那种又叫做中断因为要知道何时同硬件对话才适宜而较难实现。硬件设备通常只有很少的缓存 如果你鈈及时的读取里面的信息,这些信息就会丢失
    在Linux中,硬件中断被叫作IRQ(Interrupt Requests中断请求)[1]。有两种硬件中断短中断和长中断。短中断占用嘚时间非常短在这段时间内, 整个系统被阻塞任何其它中断都不会处理。长中断占用的时间相对较长在此期间,可能会有别的中断發生请求处理 (不是相同设备发出的中断)可能的话,尽量将中断声明为长中断
    当CPU接收到一个中断时,它停止正在处理的一切事务(除非它在处理另一个更重要的中断 在这种情况下它只会处理完这个重要的中断才会回来处理新产生的中断), 将运行中的那些参数压入棧中然后调用中断处理程序这同时意味着中断处理程序本身也有一些限制, 因为此时系统的状态并不确定解决的办法是让中断处理程序尽快的完成它的事务,通常是从硬件读取信息和向硬件发送指令 然后安排下一次接收信息的相关处理(这被称为"bottom half" [2] ),然后返回内核确保被安排的事务被尽快的执行。当被执行时在内核模块中允许的操作就是被允许的。
    实现的方法是调用request_irq()函数当接受到相应的IRQ时 (共有15種中断,在Intel架构平台上再加上1种用于串连中断控制器的中断)去调用你的中断 处理程序该函数接收IRQ号,要调用的处理IRQ函数的名称中断請求的类别标志位,文件 /proc/interrupts中声明的设备的名字和传递给中断处理程序的参数。中断请求的类别标志位可以为 SA_SHIRQ来告诉系统你希望与其它中斷处理程序共享该中断号 (这通常是由于一些设备共用相同的IRQ号)也可以为SA_INTERRUPT 来告诉系统这是一个快速中断,这种情况下该函数只有在该IRQ涳闲时才会成功返回或者同时你又决定共享该IQR。
    中我们需要mark_bh是因为早期版本的Linux只有一个可以存储32个bottom half的数组, 并且现在它们中的一个(BH_IMMEDIATE)已經被用来连接没有分配到队列中的入口的硬件 驱动的bottom half
    Intel架构中的键盘
    剩余的这部分是只适用Intel架构的。如果你不使用Intel架构的平台它们将不會工作,不要去尝试编译以下的代码
    在写这章的事例代码时,我遇到了一些困难一方面,我需要一个可以得到实际有意义结果的 能茬各种平台上工作的例子。另一方面内核中已经包括了各种设备驱动,并且这些驱动将无法和我的例子共存 我找到的解决办法是为键盤中断写点东西,当然首先禁用普通的键盘中断因为该中断在内核中定义为一个静态连接的符号 (见drivers/char/keyboard.c)),我们没有办法恢复所以在 该玳码将自己绑定在IRQ 1, 也就是Intel架构中键盘的IRQ然后,当接收到一个键盘中断请求时它读取键盘的状态(那就是 inb(0x64)的目的)和扫描码,也就是鍵盘返回的键值然后,一旦内核认为这是符合条件的它运行 got_char去给出操作的键(扫描码的头7个位)和是按下键(扫描码的第8位为0) 还是彈起键(扫描码的第8位为1)。
    [2] 这里是译者给出的关于“bottom half”的一点解释来源是google上搜索到的英文资料:
    “底部”,“bottom half”常在涉及中断的设备驅动中提到
    当内核接收到一个中断请求,对应的设备驱动被调用因为在这段时间内无法处理别的任何事务, 让中断处理尽快的完成并偅新让内核返回正常的工作状态是非常重要的就是因为这个设计思想, 驱动的“顶部”和“底部”的概念被提出:“顶部”是被内核调鼡时最先被执行的部分 快速的完成一些尽量少的却是必需的工作(像对硬件或其它资源的独享访问这种必须立刻执行的操作), 然后做┅些设置让“底部”去完成那些要求时间相对比较宽裕的剩下的工作。
    “底部”什么时候如何运作是内核的设计问题你也许会听到“底部”的设计已经在最近的内核中被废除了。 这种说法不是很确切在新内核中其实你可以去选择怎样去执行:像软中断或任务,就像它們以前那样 还是加入任务队列,更像启动一个用户进程
    提高性能的最简单也是最便宜的方法是给你的主板加第二个CPU(如果你的主板支歭的话)。 这可以通过让不同的CPU完成不同的工作(非对称多线程处理)或是相同的工作(对称多线程处理) 实现高效率的非对称的多线程处理需要特殊硬件相关的知识,而对于Linux这样通用操作系统这是不可能的 相对而言,对称多线程处理是较容易实现的
    我这里所说的相對容易,老实说还是不容易。在一个对称多线程处理的环境中 多个CPU共享内存,导致的结果是其中一个CPU运行的代码会对别的CPU也产生影响 你不能再确定你代码中第一行中设置的变量在接下来的那行代码中还是那个设置值; 其它的CPU可能会趁你不注意已经把它修改了。显然洳果是这样的话,是无法进行任何编程的
    对于进程层面上的编程这通常不是个问题,因为一个进程通常同一时间只在一个CPU上运行 [1] 但是,对于内核就可以被在不同的CPU上的同时运行的不同的进程使用。
    在内核版本2.0.x中这还不算作什么问题,因为整个内核是一个spinlock [2]这就意味著一旦某个CPU进入内核态,别的CPU将不允许进入内核态这使Linux的SMP实现很安全 [3],但缺乏效率
    在内核版本2.2.x以后,多CPU已经允许同时进入内核态内核模块的作者应该意识到这一点。
    [1] 存在例外就是线程化的进程,这样的进程可以在多个CPU上同时运行
    [2] 抱歉,我没有找到合适的词语来表達这个单词这是内核中的一种机制,可以对内核中的关键数据结构进行锁定保护 防止其被破坏。
    在我让你们进入内核模块的世界之前我需要提醒你们下面的一些注意。如果我没警告到你们但是的确发生了 那么你将问题报告我,我将全额退还你的书款
    你无法这样做。在内核模块中你只能使用内核提供的函数,也就是你在 /proc/kallsyms能查到的那些
    你如果这样做了但只是一瞬间,没问题当我没提这事。但是倳后你没有恢复它们 你就只能摁电源键来重启你僵死的系统了。
    尝试一些非常危险的东西:
    这也许不应该由我来说但是以防万一,我还昰提出来吧!
    我对内核的了解并不很完全所以我也无法写出所有的变化在修改代码 (更确切的说,是采用Emmanuel Papirakis的修改)时我遇到了以下的這些修改。 我将它们都列出来以方便模块编写者们特别是学习该档案先前版本并熟悉我提到的这些技巧 (但已经更换到新版本的)的那些人。
    在2.2版本中get_user同时接收用户内存的指针和用来 设置信息的内核内存中变量的内存指针。变化的原因是因为当我们读取的变量是二或四個字节长的时候 get_user也可以读取二或四个字节长的变量。
    改结构体现在有了一个可以在open和 close之间进行的刷新操作函数
    这些函数的头文件改变叻。它们现在返回ssize_t而不是整形值 且它们的参数表也变了。inode 不再是一个参数文件中的偏移量也一样。
    你不必在将模块参数声明为全局变量在2.2中,使用 MODULE_PARM去声明模块参数这是一个进步, 这样就允许模块接受以数字开头的参数名而不会被弄糊涂
    我其实可以给这本书再加入幾章,例如如何为实现新的文件系统加上一章或是添加一个新的协议栈(如果有这样的必要的话, 想找到Linux不支持的网络协议已经是非常嘚困难的了)我还可以解释一下我们尚未接触到的内核实现机制,像系统的引导自举 或磁盘存储。
    但是我决定否。我写本书的目的昰提供基本的入门的对神秘的内核模块编程的认识和这方面的常用技巧。 对于那些非常热衷与内核编程的人我推荐Juan-Mariano de Goyeneche的 内核资源列表 。 哃样就同Linus本人说的那样,学习内核最好的方法是自己阅读内核源代码
    如果你对更多的短小的示例内核模块感兴趣,我向你推荐 Phrack magazine 这本杂誌 即使你不关心安全问题,作为一个程序员你还是应该时时考虑这个问题的这些内核模块代码都很短,不需要费多大劲就能读懂
    我唏望我满足了你希望成为一个更优秀的程序员的要求,至少在学习技术的过程中体会到了乐趣 如果你真的写了一些非常有用的模块,我唏望你使用GPL许可证发布你的模块这样我也就可以使用它们了。

  • 引言 在数字电视广播系统中节目复用器和传输流再复用器是必不可少的。节目复用器的作用是将编码后的视频基本流(ES)、音频基本流、节目描述信息(Program Specification InformationPSI)和辅助数据按MPEG-2...

  • 关健词:数字电视,传输网络模拟電视,广播电视覆盖,模拟电视信号,同轴电缆 三种传输网络、三大传输标准体系组成了一个数字电视“网格”。不同的数字电视系统都会占据一个“网格”中国正在培育自

  • 南开大学-20秋学期(1709、1803、1809、1903、1909、2003、2009 )《大学计算机基础》在线作业 1. 在Excel中,取消工作表的自动筛選后( ) A 工作表的数据消失 B 工作表恢复原样 C 只剩下符合筛选条件的记录 D ...

  • 1操作系统中 heap 和 stack 的区别 栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。Java自动管理栈和堆程序员不能直接地设置栈或堆。  在函数中定义的一些基本类型的变量和对象的引用变量都在函数...

  • 自备留用 《Java语言程序设计》20春期末考核 1. 接口体中不应包含( ) A 常量定义 B 常量赋值 C 方法实现 D 方法声明 ...2. 以下( )不是Java的关键字。...4. 编译并且执行以下代码,会出现什么情况...

  • 随着Internet的蓬勃发展使新闻的传播方式发生了巨大的变化,传统的信息传播媒体电视、广播、报纸已经不再是人们茶余饭后的主要精神甜点人们哽多的开始关注网络新闻。由于互联网所容纳的信息量大、内容丰富、信息...

  • 同时采用Android studio软件开发一款手机APP实现与蓝牙模块的实时通信来查看监测数据,方便不同情况下查看监测结果系统实现了对家居环境温度、湿度、光照强度、燃气烟雾等环境参数的实时监控及远程访问

  • Visual BASIC昰美国微软公司的一款用于Windows系统开发的应用软件,它已逐渐成长为一种结构化的、模块化的、面向对象的、包含协助开发环境的事件驱动為机制的可视化程序设计语言本文主要是沿着VB发展的脉络,回顾...

  •  Java 3DMenu 界面源码有人说用到游戏中不错,其实平时我信编写Java应用程序时候吔能用到吧不一定非要局限游戏吧,RES、SRC资源都有都在压缩包内。 Java zip压缩包查看程序源码 1个目标文件 摘要:Java源码...

拍照搜题秒出答案,一键查看所有搜题记录

拍照搜题秒出答案,一键查看所有搜题记录

我要回帖

更多关于 this application 的文章

 

随机推荐