ATOM快捷键操作

光标移动

·       Alt+Left or Alt+B – Move to the beginning of word

·       Alt+Right or Alt+F – Move to the end of word

·       Cmd+Left or Ctrl+A – Move to the first character of the current line

·       Cmd+Right or Ctrl+E – Move to the end of the line

·       Cmd+Up – Move to the top of the file

·       Cmd+Down – Move to the bottom of the file

·       Ctrl+P – Go up a single character

·       Ctrl+N – Go down a single character

·       Ctrl+B – Go left to a single character

·       Ctrl+F – Go right to a single character

·       Ctrl+G – Go to a certain line

·       Cmd+R – Jump to a symbol such as a method definition

·       Cmd+Shift+R – Search for symbols across the project

·       Alt+Cmd+Down to go to and Alt+Cmd+Up to return from the declaration of the symbol under the cursor

选择文本

·       Shift+移动快捷键

·       Cmd+A – Select the entire contents of the file

·       Cmd+L – Select the entire line

·       Ctrl+Shift+W – Select the current word

编辑文本

·       Cmd+J – Join the next line to the end of the current line

·       Cmd+Ctrl+Up/Down – Move the current line up or down

·       Cmd+Shift+D – Duplicate the current line

·       Cmd+K Cmd+U – Upper case the current word

·       Cmd+K Cmd+L – Lower case the current word

·       Ctrl+T – Transpose characters. This swaps the two characters on either side of the cursor.

·       Ctrl+Shift+K – Delete current line

·       Alt+Backspace or Alt+H – Delete to beginning of word

·       Alt+Delete or Alt+D – Delete to end of word

·       Cmd+Delete – Delete to end of line

·       Ctrl+K – Cut to end of line

·       Cmd+Backspace – Delete to beginning of line

多光标操作

·       Cmd+Click – Add a new cursor at the clicked location

·       Ctrl+Shift+Up/Down – Add another cursor above/below the current cursor

·       Cmd+D – Select the next word in the document that is the same as the currently selected word

·       Cmd+Ctrl+G – Select all words in the document that are the same as the currently selected word

·       Cmd+Shift+L – Convert a multi-line selection into multiple cursors

查找和替换文本

·       Cmd+F – Search within a buffer

·       Cmd+Shift+F – Search the entire project

 

HTML概要

HTML CSS Javascript 的关系

HTML是网页内容的载体。内容就是网页制作者放在页面上想要让用户浏览的信息,可以包含文字、图片、视频等。

CSS样式是表现。就像网页的外衣。比如,标题字体、颜色变化,或为标题加入背景图片、边框等。所有这些用来改变内容外观的东西称之为表现。

JavaScript是用来实现网页上的动态效果。如:鼠标滑过弹出下拉菜单。或鼠标滑过表格的背景颜色改变。还有焦点新闻(新闻图片)的轮换。

HTML 标签语法

1标签由英文尖括号<>括起来,如<html>就是一个标签

2. html中的标签一般都是成对出现的,分开始标签结束标签。结束标签比开始标签多了一个/

如:

1) <p></p>

2) <div></div>

3) <span></span>

3. 标签与标签之间是可以嵌套的,但先后顺序必须保持一致,如:<div>里嵌套<p>,那么</p>必须放在</div>的前面。如下图所示

4. HTML标签不区分大小写,<h1><H1>是一样的,但建议小写,因为大部分程序员都以小写为准。

HTML标签

<p> 标签

如果想在网页上显示文章,就需要<p>标签,把文章的段落放到<p>标签中

语法

<p>段落文本</p>

 

<hx> 标签

使用<hx>标签来制作文章的标题
标题标签一共有6个,h1h2h3h4h5h6分别为一级标题、二级标题、三级标题、四级标题、五级标题、六级标题。并且依据重要性递减。<h1>是最高的等级

语法

<h1>段落文本</h1>

<strong> <em>标签

在一段话中特别强调某几个文字,这时候就可以用到<em><strong>标签。

但两者在强调的语气上有区别:<em> 表示强调,<strong> 表示更强烈的强调。并且在浏览器中<em> 默认用斜体表示,<strong> 粗体表示

语法

<em>段落文本</em>

<strong>段落文本</strong>

<q> 标签

使用q标签可以在html中添加一段引用,如作家的话诗句等。

1注意要引用的文本不用加双引号,浏览器会对q标签自动添加双引号

语法

段落文本<q>引用文本</q>段落文本

<blockquote> 标签

 blockquote作用也是引用别人的文本。但它是对长文本的引用,如在文章中引入大段某知名作家的文字,这时需要这个标签。

1浏览器对<blockquote>标签的解析是缩进样式,而不是添加引号

语法

<blockquote>引用段落</blockquote>

<br /> 标签

 需要加回车换行的地方加入<br /><br />标签作用相当于word文档中的回车。

1浏览器会忽略HTML中的回车和空格,如果需要换行,就要用到<br />标签。

2. 如果需要加空格,则需要用&nbsp来替换空格。

语法

引用段落<br />

<hr /> 标签

在信息展示时,有时会需要加一些用于分隔的横线,这样会使文章看起来整齐.

1. <hr />标签和<br />标签一样也是一个空标签,所以只有一个开始标签,没有结束标签

2. <hr />标签的在浏览器中的默认样式线条比较粗,颜色为灰色。可以通过css来改变水平线的样式。

语法

<p>文本段落</p>

<hr />

<address> 标签

一般网页中会有一些网站的联系地址信息需要在网页中展示出来,这些联系地址信息如公司的地址就可以<address>标签。也可以定义一个地址(比如电子邮件地址)、签名或者文档的作者身份

1. <address>标签中的内容会在浏览器中显示为斜体。

语法

<address>地址信息或联系人信息</address>

<code><pre> 标签

在介绍语言技术的网站中,避免不了在网页中显示一些计算机专业的编程代码,当代码为一行代码时,你就可以使用<code>标签了,如下面例子:

<code>var i=i+300;</code>

1. 如果是多行代码,可以使用<pre>标签。

2. <pre> 标签的主要作用:预格式化的文本。被包围在 pre 元素中的文本通常会保留空格和换行符。

语法

<code>一行计算机代码</code>

<pre>多行计算机代码</pre>

<ul> <li>标签

利用<ul><li>可以生成没有顺序的列表。

语法

<ul>

  <li>精彩少年</li>

  <li>美丽突然出现</li>

  <li>触动心灵的旋律</li>

</ul>

<ol> <li>标签

利用<ol><li>可以生成有顺序的列表。

语法

<ol>

  <li>前端开发面试心法 </li>

  <li>零基础学习html</li>

  <li>JavaScript全攻略</li>

</ul>

<div> 标签

在网页制作过程过中,可以把一些独立的逻辑部分划分出来,放在一个<div>标签中,这个<div>标签的作用就相当于一个容器

1. divspan类似,都没有特殊的语义。

2. div可以用来排版。

3. div属于块级元素。

4. 可以用id来为div分组命名

语法

<div id=’xxx’>

  

</div>

<caption> 标签

可以使用caption标签来为表格添加标题:

语法:

<table>

  <caption>标题文本</caption>

  <tr>

    <td>…</td>

    <td>…</td>

     

   </tr>

   

</table>

<a> 标签

使用<a>标签可实现超链接,它在网页制作中可以说是无处不在,只要有链接的地方,就会有这个标签

语法:

<a href=”目标网址” title=”鼠标滑过显示的文本“>链接显示的文本</a>

1. title属性的作用鼠标滑过链接文字时会显示这个属性的文本内容。这个属性在实际网页开发中作用很大,主要方便搜索引擎了解链接地址的内容(语义化更友好)

2. <a>标签在默认情况下,链接的网页是在当前浏览器窗口中打开,如果需要在新的浏览器窗口中打开,则需要用到target选项。

<a href=”目标网址” target=”_blank”>click here!</a>
3. a标签还有一个作用是可以链接Email地址,使用mailto能让访问者便捷向网站管理者发送电子邮件

<img> 标签

使用<img>标签可以在网页中插入图片。

语法:

<img src=”图片地址” alt=”下载失败时的替换文本” title = “提示文本“>
1src标识图像的位置

2alt指定图像的描述性文本,当图像不可见时(下载不成功时),可看到该属性指定的文本

3title提供在图像可见时对图像的描述(鼠标滑过图片时显示的文本)

4图像可以是GIFPNGJPEG格式的图像文件。

HTML表单

表单可以把浏览者输入的数据传送到服务器端,这样服务器端程序就可以处理表单传过来的数据

语法:

<form method=”传送方式” action=”URL”>

</form>

1.<form> <form>标签是成对出现的,以<form>开始,以</form>结束

2.action 浏览者输入的数据被传送到的地方,比如一个PHP页面(save.php)

3.method  数据传送的方式(get/post

4. 所有表单控件(文本框、文本域、按钮、单选框、复选框等)都必须放在 <form></form> 标签之间
5. get请求会把表单提供的参数放到URL中,而post请求会把参数放到http请求体中

文本、密码输入框

当用户要在表单中键入字母、数字等内容时,就会用到文本输入框。文本框也可以转化为密码输入框

语法:

<form>

  <input type=”text/password” name=”名称” value=”文本” />

</form>

1type

   当type=”text”时,输入框为文本输入框;

   当type=”password”输入框为密码输入框

2name:为文本框命名,以备后台程序ASP PHP使用

3value:为文本输入框设置默认值。(一般起到提示作用)

文本域 多行文本输入

当用户需要在表单中输入大段文字时,需要用到文本输入域。

语法:

<textarea rows=”行数 cols=”列数>

文本

</textarea>

1<textarea>标签是成对出现的,以<textarea>开始,以</textarea>结束

2cols 多行输入域的列数

3rows 多行输入域的行数

4、在<textarea></textarea>标签之间可以输入默认值

单选框、复选框

在使用表单设计调查表时,为了减少用户的操作,使用选择框是一个好主意,html中有两种选择框,即单选框复选框,两者的区别是单选框中的选项用户只能选择一项,而复选框中用户可以任意选择多项,甚至全选

语法:

<input type=”radio/checkbox” value=”” name=”名称” checked=”checked”/>

1type:

   当 type=”radio” 时,控件为单选框

   当 type=”checkbox” 时,控件为复选

2value:提交数据到服务器的值(后台程序PHP使用

3name:为控件命名,以备后台程序 ASPPHP 使用

4checked:当设置 checked=”checked” 时,该选项被默认选中

下拉列表框

下拉列表在网页中也常会用到,它可以有效的节省网页空间。既可以单选、又可以多选

下拉列表也可以进行多选操作,在<select>标签中设置multiple=”multiple”属性,就可以实现多选功能

提交按钮

在表单中有两种按钮可以使用,分别为:提交按钮、重置。这一小节讲解提交按钮:当用户需要提交表单信息到服务器时,需要用到提交按钮

语法

<input type=”submit” value=”提交“>

1, type:只有当type值设置为submit时,按钮才有提交作用

2, value按钮上显示的文字

重置按钮

当用户需要重置表单信息到初始时的状态时,比如用户输入“用户名”后,发现书写有误,可以使用重置按钮使输入框恢复到初始状态。只需要把type设置为“reset”就可以

语法

<input type=”reset” value=”重置“>

1, type:只有当type值设置reset,按钮才有重置作用

2, value按钮上显示的文字

form表单中的label标签

label标签不会向用户呈现任何特殊效果,它的作用是为鼠标用户改进了可用性。如果你在 label 标签内点击文本,就会触发此控件

语法

<label for=”控件id名称“> </label>

HTML5

更简化的语法:

  • <!doctype html>
  • <html lang=“zh-CN”>
  • <meta charset=“utf-8”>
  • 不区分大小写  HTML = HtMl
  • checked=“checked” à checked
  • <html><head><body>可以省略

      。。。

废除的标签

  • 能用css替代的标签:basefont, font, center
  • Frame相关的标签,只支持iframeframeset, frame, noframes
  • 只有部分浏览器支持的标签:applet, bgsound
  • 其他废除的标签:rb, dir, listing, xmp

废除了一些和样式相关的属性

HTML5 新增标签

  • <section></section> 表示页面中的一个内容区块,不用作排版
  • <article></article> 表示与上下文不相干的独立内容,比如说文章
  • <aside></aside> 表示与文章相关的辅助信息,如文章后面的推荐阅读
  • <header></header> 页眉
  • <footer></footer> 页脚
  • <nav></nav> 导航栏
  • <figure></figure> 标签规定独立的流内容(图像、图表、照片、代码等等)。figure 元素的内容应该与主内容相关,但如果被删除,则不应对文档流产生影响
  • <figcaption></figcaption> 标签定义 figure 标题(caption)。figcaption元素应该被置于 “figure” 元素的第一个或最后一个子元素的位置
  • <video></video> 视频标签
  • <audio></audio> 音频标签
  • <embed /> 嵌入标签,可以是视频、音频、flash等多媒体内容
  • <mark></mark> 替换文本的背景色来标记文本
  • <progress max=“100” value=“85”></progress> 进度条
  • <canvas></canvas> 绘图,替代flash
  • <details><summary>always show</summary>something can be hidden</details>
  • <datalist></datalist>

HTML5 新增属性

  • <html manifest=“xxx.manifest”>
  • <meta charset=“utf-8”>
  • <link rel=‘icon’ href=“URL” type=image/gif sizes=“16×16”> //目前几乎没有主流浏览器支持 sizes 属性
  • <script defer src=“*.js”></script>
  • <script async src=“*.js”></script>
  • <a media=“tv href=“www.baidu.com hreflang=“zh ref=“external”>baidu</a>
  • <ol start=”50” reversed></ol>
  • <style scoped></style>
  • <iframe seamless srcdoc=“<h1>hello</h1>” sandbox src=www.baidu.com> </iframe>

HTML5 新增全局属性

  • data-*
  • Hidden
  • Tabindex
  • spellcheck=”true”
  • contenteditable=“true”

 

Python中list的遍历

在python中,若要遍历一个list而且需要在遍历时修改list,则需要十分注意,因为这样可能会导致死循环,例如:

In [10]: ls = ['hello', 'world', 'bugggggggg']

In [11]: for item in ls:
   ....:     if len(item) > 5:
   ....:         ls.insert(0, item)
   ....:         print ls
   ....:         
['bugggggggg', 'hello', 'world', 'bugggggggg']
['bugggggggg', 'bugggggggg', 'hello', 'world', 'bugggggggg']
['bugggggggg', 'bugggggggg', 'bugggggggg', 'hello', 'world', 'bugggggggg']
...

所以,为了安全起见,在遇到需要修改列表的时候,都不对列表本身进行遍历,而是创建一个列表的备份,然后对这个备份进行遍历,从而避免了上述情形。例如:

In [20]: In [10]: ls = ['hello', 'world', 'bugggggggg']

In [21]: for item in ls[:]:
   ....:     if len(item) > 5:
   ....:         ls.insert(0, item)
   ....:         

In [22]: print(ls)
['bugggggggg', 'hello', 'world', 'bugggggggg']

 

Python中的参数传递与解析

Python传递命令行参数

Python的命令行参数传递和C语言类似,都会把命令行参数保存到argv的变量中。对于python而言,argv是sys模块中定义的一个list。与C语言不同的是,python中并没有定义argc,要获得参数的个数,需要使用len(sys.argv)

当用户使用’python -c “command” ‘来运行一条python语句时,argv中保存的是[‘-c’]及”command”后面的参数,例如:

$ python -c 'import sys
print sys.argv' hello world
['-c', 'hello', 'world']

当用户使用’python -m “module” ‘来运行一个模块时,argv中保存的是模块名及”module”后面的参数,例如:

$ python -m 'show_args' hello world
['/home/kelvin/tmp/show_args.py', 'hello', 'world']

当运行python脚本时,argv中保存的是脚本名及其后面的参数:

$ python show_args.py hello world
['show_args.py', 'hello', 'world']
length of argv: 3

$ cat show_args.py 
#!/bin/env python

import sys

print sys.argv
print "length of argv: " + str(len(sys.argv))

使用标准库getopt解析选项和参数

getopt模块和C语言中的getopt函数有着一样的API,熟悉C语言的同学可快速上手。

# -*- coding: utf-8 -*-
#!/bin/env python

import getopt
import sys


def usage():
    print("Usage:")
    print(sys.argv[0] + ' -i input_file -o output_file')


def main():
    try:
        opts, args = getopt.getopt(sys.argv[1:], "ho:i:", ["help", "output=", "input="])
    except getopt.GetoptError as err:
        # print help information and exit:
        print("[" + err.opt + "]" + err.msg)
        usage()
        sys.exit(2)
    output = None
    input = None
    for o, a in opts:
        if o in ("-i", "--input"):
            input = a
        elif o in ("-h", "--help"):
            usage()
            sys.exit()
        elif o in ("-o", "--output"):
            output = a
        else:
            assert False, "unhandled option"
    print("Input file: %s" % (input))
    print("Output file: %s" % (output))

if __name__ == "__main__":
    main()

getopt需要传入3个参数:

1.  需要解析的字符串,即sys.argv[1:]

2. 短选项集合。其中跟冒号的短选项需要后接参数,如’o:’表示’-o’选项需要接参数。

3. 长选项列表。其中跟等号的长选项需要后接参数。

getopt返回一个元组,元组包括两个列表opts和args。opts的元素是一个元组,保存了解析好的选项和参数对。args保存了除去所有选项和选项的参数之外,剩下的所有参数。

如果解析出错则会抛出GetoptError异常,该异常有一个参数err。err.opt是出错时正在解析的选项,err.msg是错误消息。

出错的情况包括:

1. 选项没有在传入参数中的短选项或者长选项列表定义。

2. 需要带参数的选项没有跟参数。

3. 不需要带参数的长选项带了参数。

4. 其他。

kelvin@kvm:~/tmp$ python parse_args.py --help=out
[help]option --help must not have an argument
Usage:
parse_args.py -i input_file -o output_file
kelvin@kvm:~/tmp$ python parse_args.py -i input_file.txt 
Input file: input_file.txt
Output file: None
kelvin@kvm:~/tmp$ python parse_args.py -i input_file.txt --output="hello_world.txt"
Input file: input_file.txt
Output file: hello_world.txt
kelvin@kvm:~/tmp$ python parse_args.py -i input_file.txt -o "output.txt"
Input file: input_file.txt
Output file: output.txt

使用标准库argparse来解析选项和参数

argparse模块功能更加强大,例如可以自动生成help文档等,使用起来也更加简便,只需要三个步骤即可。第一步,创建一个ArgumentParser对象:

parser = argparse.ArgumentParser(description='Description of the program')

第二步,添加需要解析的选线和参数:

parser.add_argument('integers', metavar='N', type=int, nargs='+',
                    help='an integer for the accumulator')
parser.add_argument('--sum', dest='accumulate', action='store_const',
                    const=sum, default=max,
                    help='sum the integers (default: find the max)')

第三部,开始解析:

args = parser.parse_args()

ArgumentParser

 class argparse.ArgumentParser(prog=None, usage=None, 
description=None, epilog=None, parents=[], 
formatter_class=argparse.HelpFormatter, prefix_chars='-',
 fromfile_prefix_chars=None, argument_default=None, 
conflict_handler='error', add_help=True, allow_abbrev=True)

常用的参数解释如下:

prog: 指定程序的名字,默认为sys.argv[0].

usage: 描述程序该如何使用的字符串,默认会根据添加的参数和选项自动生成

description: 描述程序的功能,默认为空

epilog: epilog指定的字符串将会显示在帮助文档的最后

parents: 一个 ArgumentParser对象的列表,这些对象的选项和参数也会被继承

add_help: 添加-h/–help选项,默认为True

allow_abbrev: 允许长选项的缩写,默认为True

add_argument

 ArgumentParser.add_argument(name or flags...[, action]
[, nargs][, const][, default][, type][, choices][, required]
[, help][, metavar][, dest])

name将会作为解析后返回的对象args的属性,存储参数的值,flags定义指定的选项,flag的名字也会作为解析后返回的对象的属性,存储该选项的参数。例如:

parser.add_argument('-f', '--foo')
parser.add_argument('arg0')
args = parser.parse_args()
print(args.foo)
print(args.arg0)

执行结果如下:

$./arg_parse.py --foo hello world
hello
world

nargs指定选项参数或者参数本身的个数。例如:

parser.add_argument('-f', '--foo', nargs=2)
args = parser.parse_args()
print(args.foo)

执行结果如下:

$./arg_parse.py --foo hello world
['hello', 'world']

argparse会将–foo选项后面的两个参数都作为–foo的参数处理。

action指定argparse如何处理该选项的参数,共有8个值可选。

  1. ‘store’: 默认值,表示存储参数,如上面例子中的args.foo存储hello world.
  2. ‘store_const’: 存储常量,常量的值位于const参数中。如:
    $ cat arg_parse.py 
    #!/usr/bin/env python
    
    import argparse
    
    parser = argparse.ArgumentParser()
    parser.add_argument('-f', '--foo', action='store_const', const=1024)
    args = parser.parse_args()
    print(args.foo)
    
    $ ./arg_parse.py -f
    1024
    

     

  3. ‘store_true’或者’store_false’:和’store_const’类似,存储的值为True或者False

  4. ‘append’:连个同样的选项的参数会被放到同一个list里面,例如:
     

    $ cat arg_parse.py 
    #!/usr/bin/env python
    
    import argparse
    
    parser = argparse.ArgumentParser()
    parser.add_argument('-f', '--foo', action='append')
    args = parser.parse_args()
    print(args.foo)
    
    $ ./arg_parse.py --foo 1 --foo 2 -f 3
    ['1', '2', '3']

     

  5. ‘append_const’: 可将多个常量存放到一个list中,选项出现几次,list中的常量就出现几次,例如:
     

    $ cat arg_parse.py 
    #!/usr/bin/env python
    
    import argparse
    
    parser = argparse.ArgumentParser()
    parser.add_argument('-f', '--foo', action='append_const', const="1024")
    args = parser.parse_args()
    print(args.foo)
    
    $ ./arg_parse.py --foo --foo -f
    ['1024', '1024', '1024']

     

  6. ‘count’:  存储选项出现的次数。例如:
     

    $ cat arg_parse.py 
    #!/usr/bin/env python
    
    import argparse
    
    parser = argparse.ArgumentParser()
    parser.add_argument('-v', '--verbose', action='count')
    args = parser.parse_args()
    print(args.verbose)
    
    $ ./arg_parse.py -vvv
    3
    
    $ ./arg_parse.py -v --verbose -v
    3

     

  7. ‘help’: 当出现这个选项时,程序打印help文档然后退出。

  8. ‘version’: 当出现这个选项时,程序打印版本信息然后退出,版本信息可通过version定义,例如:
     

    $cat arg_parse.py
    #!/usr/bin/env python
    
    import argparse
    
    parser = argparse.ArgumentParser(prog="wchat")
    parser.add_argument('-v', '--version', action='version', version='%(prog)s 3.8.5')
    args = parser.parse_args()
    
    $ ./arg_parse.py --version
    wchat 3.8.5

     

required指定该参数或者选项是必须提供的,否则会报错退出。

type指定参数的类型,可以是任何python内建的数据类型如int等,也可以是自定义的类型转换函数的函数名。例如:

$ cat ./arg_parse.py 
#!/usr/bin/env python

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('-f', '--foo', type=int)
args = parser.parse_args()
print(args.foo)

$ ./arg_parse.py -f 12
12

$ ./arg_parse.py -f '12'
12

$ ./arg_parse.py -f hello
usage: arg_parse.py [-h] [-f FOO]
arg_parse.py: error: argument -f/--foo: invalid int value: 'hello'

choices指定一组参数,选项的参数必须从这组参数中来选取。例如:

$ cat arg_parse.py 
#!/usr/bin/env python

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('-f', '--foo', type=int, choices=range(1, 4))
args = parser.parse_args()
print(args.foo)

$ ./arg_parse.py -f3
3

$ ./arg_parse.py -f19
usage: arg_parse.py [-h] [-f {1,2,3}]
arg_parse.py: error: argument -f/--foo: invalid choice: 19 (choose from 1, 2, 3)

help指定参数或者选项的帮助信息,会出现在help文档里。

metavar可以改变帮助文档中选项的参数占位字符串,例如,–foo默认的占位字符串为FOO,可以通过metavar改为foo_arg:

$ cat arg_parse.py 
#!/usr/bin/env python

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('-f', '--foo')
args = parser.parse_args()
print(args.foo)

$ ./arg_parse.py -h
usage: arg_parse.py [-h] [-f FOO]

optional arguments:
  -h, --help         show this help message and exit
  -f FOO, --foo FOO

$ cat arg_parse.py 
#!/usr/bin/env python

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('-f', '--foo', metavar='foo_arg')
args = parser.parse_args()
print(args.foo)

$ ./arg_parse.py -h
usage: arg_parse.py [-h] [-f foo_arg]

optional arguments:
  -h, --help            show this help message and exit
  -f foo_arg, --foo foo_arg

dest可以修改存储参数的属性名,例如:

$ cat arg_parse.py 
#!/usr/bin/env python

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('-f', '--foo', dest='bar')
args = parser.parse_args()
print(args.bar)

$ ./arg_parse.py -f hello
hello

小结

getopt虽然提供了接近Unix C的用户接口,方便了熟悉Unix C的程序猿/媛们,但argparse模块功能更为强大,使用起来也更为简洁,所以大多数的python项目都采用argparse来解析参数。

基于web技术的操作系统安装器的设计

什么是基于web技术的安装器(web-based installer)?

传统的Linux操作系统安装需要启动一个LiveOS,然后在LiveOS中运行一个本地安装程序,如Fedora下的Anaconda. LiveOS除了让用户在安装操作系统之前能预先体验之外,也为安装器提供了运行环境。这对于桌面操作系统已然足够,因为PC、笔记本电脑自带终端设备——键盘、显示器、鼠标。然而,如果要给一台服务器安装操作系统则稍微复杂了一点,因为服务器通常没有这些终端设备。这就需要利用网络和VNC将服务器端的图像传送出来。这样做的缺点在于,网络负载很大,在网络条件不好的环境下会带给用户非常差的安装体验。另外,本地安装器也会依赖很多的图形软件包,不仅会增加ISO的大小,也会带来很多版权及法律上的工作量。

基于web技术的安装器则利用web开发技术——HTML5,Javascript,CSS,web server等,让用户可以通过浏览器直接安装操作系统。目前,”KVM for IBM z Systems”已经采用这种安装方式。

基于web技术安装器的优势

  • 依赖更少的软件包
  • 非常小的网络负载,网页加载后只需要通过Ajax来与服务器通信
  • 可通过浏览器跨平台访问
  • 对于服务器及集群安装非常方便
  • 易于与其他软件集成,提供RESTful API
  • 满足可访问性,方便残障人士使用

使用web安装器的安装流程

使用web安装器来安装操作系统的流程非常简单,只需要如下几个步骤:

  1. 加载ISO
  2. 用https://{IP_OF_SERVER}作为URL来访问安装器
  3. 按照安装器的引导完成安装
  4. 点击安装器的重启按钮重启服务器,安装完成

Web安装器实现的功能

  • 国际化及多语言支持,选择安装语言与系统语言
  • 版权声明
  • 磁盘列表及选择安装磁盘
  • 添加SCSI磁盘
  • 添加DASD磁盘
  • Swap分区加密
  • 自动分区
  • 手动分区
  • 分区操作列表
  • 激活网卡
  • IPv4配置
  • IPv6配置
  • 主机名及搜索域配置
  • NTP配置
  • Kdump配置
  • 时区设置
  • Root密码设置
  • 添加、删除用户
  • 配置总结列表
  • 无人值守安装
  • 安装日志下载

Web安装器的架构设计

Web安装器分为前端和后端两部分实现:前端负责UI展示及用户交互,后端负责给前端提供RESTful API并根据前端的API调用来存储用户配置数据,执行分区、安装、配置目标系统等操作。

Web安装器由4个HTML页面组成:

  • 欢迎页:介绍操作系统,提供选择安装语言的下拉框,点击下一步可进入到版权声明页
  • 版权声明页:显示版权文件,在用户同意后可跳转到配置页面
  • 配置页:引导用户进行系统配置
  • 安装页:展示安装进度,安装完成后可点击重启按钮重启系统

UI是基于HTML5、CSS3及Javascript等网页开发技术,并利用如下工具:

  • jQuery:一个快速、小巧且功能丰富的js库,可用来操作DOM,处理事件及Ajax请求
  • Bootstrap:最流行的前端开发框架之一,多用于开发响应式、移动优先的web项目
  • Bootstrap-select: jQuery 插件,利用Bootstrap,但提供了功能更加丰富的下拉选择框控件
  • Jquery Validation :jQuery插件,用来检验表单的合法性

后端由以下几个模块组成:

  • CherryPy:一个轻量级的python web发布器
  • Model:存储用户的配置数据
  • RESTful API:为前端提供API接口
  • 子功能模块:提供安装器的各个子功能

QEMU3 – 使用ceph来存储QEMU镜像

ceph简介

Ceph是一个PB级别的分布式软件定义存储系统,为用户提供了块存储、对象存储以及符合POSIX标准的文件系统接口。目前,Ceph已经成为Openstack最受欢迎的后端存储系统。下图为ceph的架构图。

ceph架构图

RADOS本身是一个对象存储系统,实现了ceph的核心功能。Librados是ceph提供给各种编程语言的接口。RADOSGW,RBD,CEPH FS分别为用户提供了对象存储、块存储及文件系统的功能。Ceph集群及客户端的安装配置请参考Ceph官方文档

使用Ceph来存储QEMU镜像

QEMU会假定ceph配置文件存放在默认位置/etc/ceph/$cluster.conf,也会使用client.admin作为默认的ceph用户。如果要指定其他的配置文件或者用户,可以在ceph RBD的选项中添加conf=/home/ceph.conf或者id=admin选项。qemu-img使用ceph块存储RBD时,需要使用下面的格式:

qemu-img {command} [options] rbd:{pool-name}/{image-name}[@snapshot-name][:option1=value1][:option2=value2…] 

例如:

qemu-img {command} [options] rbd:glance-pool/maipo:id=glance:conf=/etc/ceph/ceph.conf 

创建一个镜像

可以使用qemu-img命令在ceph集群中创建一个虚拟机镜像。需要指定rbd, pool,以及镜像名。

qemu-img create -f raw rbd:{pool-name}/{image-name} {size} 

例如:

[root@ltczhp20 ~]# qemu-img create -f raw rbd:rbd/vmdisk1 4G

Formatting 'rbd:rbd/vmdisk1', fmt=raw size=4294967296
[root@ltczhp20 ~]# rbd ls
vmdisk1

qemu-img通常会指定RBD存储的镜像格式是RAW,这样可以减少其他格式带来的性能开销,也会防止虚拟机热迁移时缓存带来的问题。

调整镜像的大小

要调整镜像大小,必须指定rbd,pool name,以及镜像名。

qemu-img resize rbd:{pool-name}/{image-name} {size} 

例如:

[root@ltczhp20 ~]# qemu-img resize -f raw rbd:rbd/vmdisk1 2G

Image resized.
[root@ltczhp20 ~]# rbd ls
vmdisk1
[root@ltczhp20 ~]# rbd info vmdisk1
rbd image 'vmdisk1':
    size 2048 MB in 512 objects
    order 22 (4096 kB objects)
    block_name_prefix: rbd_data.fa802ae8944a
    format: 2
    features: layering, exclusive-lock, object-map, fast-diff, deep-flatten
    flags:

如果不指定镜像格式(-f raw),qemu会给出警告信息:

[root@ltczhp20 ~]# qemu-img resize rbd:rbd/vmdisk1 4G
WARNING: Image format was not specified for 'rbd:rbd/vmdisk1' and probing guessed raw.
         Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
         Specify the 'raw' format explicitly to remove the restrictions.
Image resized.

获取镜像信息

获取镜像信息同样需要指定rbd,pool name以及镜像名:

qemu-img info rbd:{pool-name}/{image-name} 

例如:

[root@ltczhp20 ~]# qemu-img info rbd:rbd/vmdisk1

image: rbd:rbd/vmdisk1
file format: raw
virtual size: 4.0G (4294967296 bytes)
disk size: unavailable
cluster_size: 4194304

使用qemu命令运行虚拟机

从QEMU0.15后,虚拟机使用ceph块设备就不需要使用rbd map命令将RBD镜像映射到本地了,QEMU可以通过librados直接访问一个虚拟块设备。这样避免了额外的上下文切换,也充分利用了RBD的缓存功能。

在运行虚拟机之前,我们可以把一个已经存在的虚拟机镜像转化为ceph RBD存储,然后直接从RBD启动虚拟机。

qemu-img convert -c -f fmt -O out_fmt -o options  fname out_fname 

例如:

[root@ltczhp20 ~]# qemu-img convert -f qcow2 -O raw /srv/fedora24/fedora24.qcow2 rbd:rbd/fedora

然后使用qemu命令运行虚拟机。

[root@ltczhp20 ~]# qemu-system-s390x -nographic -enable-kvm -m 4G -drive format=raw,file=rbd:rbd/fedora

RBD缓存会极大的提高虚拟机的性能。QEMU1.2之后,cache选项可以直接控制librbd:

[root@ltczhp20 ~]# qemu-system-s390x -nographic -enable-kvm -m 4G -drive format=raw,file=rbd:rbd/fedora,cache=writeback

在QEMU1.2之前,如果要使用RBD缓存,需要额外添加rbd_cache=true选项:

[root@ltczhp20 ~]# qemu-system-s390x -nographic -enable-kvm -m 4G -drive format=raw,file=rbd:rbd/fedora,cache=writeback,rbd_cache=true

如果指定了rbd_cache=true,一定要指定cache=writeback,否则QEMU不会给librbd发送flush请求,RBD之上的文件系统可能会被破坏。

使用ceph RBD的快照功能

创建一个镜像快照sp0:

[root@ltczhp20 ~]# qemu-img snapshot -l rbd:rbd/fedora
[root@ltczhp20 ~]# qemu-img snapshot -c sp0 rbd:rbd/fedora
WARNING: Image format was not specified for 'rbd:rbd/fedora' and probing guessed raw.
         Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
         Specify the 'raw' format explicitly to remove the restrictions.
[root@ltczhp20 ~]# qemu-img snapshot -l rbd:rbd/fedora
Snapshot list:
ID        TAG                 VM SIZE                DATE       VM CLOCK
sp0       sp0                     20G 1970-01-01 01:00:00   00:00:00.000

启动虚拟机,创建文件/root/hello.txt并写入字符串”hello world”,然后关闭虚拟机。

[root@ltczhp20 ~]# qemu-system-s390x -nographic -enable-kvm -m 4G -drive format=raw,file=rbd:rbd/fedora

In VM:

[root@localhost ~]# echo "hello world" >> /root/hello.txt
[root@localhost ~]# cat /root/hello.txt
hello world

[root@localhost ~]# halt

将虚拟机回滚到快照sp0,然后检查是否存在/root/hello.txt文件,如果不存在则说明快照已经成功回滚。

[root@ltczhp20 ~]# qemu-img snapshot -a sp0 rbd:rbd/fedora

[root@ltczhp20 ~]# qemu-system-s390x -nographic -enable-kvm -m 4G -drive format=raw,file=rbd:rbd/fedora

In VM:

[root@localhost ~]# ls /root/hello.txt
ls: cannot access '/root/hello.txt': No such file or directory

[root@localhost ~]# halt

删除快照:

[root@ltczhp20 ~]# rbd snap ls rbd/fedora
SNAPID NAME     SIZE
    22 sp0  20480 MB
[root@ltczhp20 ~]# qemu-img snapshot -d sp0 rbd:rbd/fedora
WARNING: Image format was not specified for 'rbd:rbd/fedora' and probing guessed raw.
         Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
         Specify the 'raw' format explicitly to remove the restrictions.
[root@ltczhp20 ~]# rbd snap ls rbd/fedora
[root@ltczhp20 ~]# qemu-img snapshot -l rbd:rbd/fedora

阅读原文

顺序存储线性表的实现

最近复习数据结构,写了一个顺序存储的线性表,代码粘在这里:)

代码下载:git@github.com:Wang-Sen/algorithm.git

/*
 * Simple array implementation.
 */

#include <stdbool.h>

#define MAX_SIZE 50
#define data_t int
#define array_length_t int

#define ERR_OUT_OF_RANGE -1
#define ERR_EMPTY_ARRAY -2
#define ERR_INVALID_ARGS -3

typedef struct {
    data_t data[MAX_SIZE];
    array_length_t length;
} array;

static inline void array_init(array *arr)
{
    for (int i = 0; i < MAX_SIZE; i++) {
        arr->data[i] = 0;
    }
    arr->length = 0;
}

static inline void show_array(array *arr)
{
    for (int i=0; i < arr->length; i++) {
        printf("%d ", arr->data[i]);
    }
    printf("\n");
}

static inline bool is_array_empty(array *arr)
{
    if (!arr) {
        return ERR_INVALID_ARGS;
    }
    return !arr->length;
}

static inline array_length_t len(array *arr) {
    if (!arr) {
        return ERR_INVALID_ARGS;
    }
    return arr->length;
}

static inline int locate_entry(array *arr, data_t entry) {
    if (!arr) {
        return ERR_INVALID_ARGS;
    }
    int ret = -1;
    for (int i = 0; i < MAX_SIZE; i++) {
        if (arr->data[i] == entry) {
            ret = i;
            return ret;
        }
    }
    return ret;
}

static inline int array_push(array *arr, data_t entry)
{
    if (!arr) {
        return ERR_INVALID_ARGS;
    }
    if ((arr->length + 1) > MAX_SIZE) {
        return ERR_OUT_OF_RANGE;
    }
    arr->data[arr->length] = entry;
    (arr->length)++;
    return 0;
}

static inline int array_pop(array *arr, data_t *p_entry)
{
    if (arr->length <= 0) {
        return ERR_EMPTY_ARRAY;
    }
    if (!p_entry || !arr) {
        return ERR_INVALID_ARGS;
    }

    *p_entry = arr->data[arr->length - 1];
    (arr->length)--;

    return 0;
}

static inline int array_insert(array *arr, int loc, data_t entry)
{
    if (!arr) {
        return ERR_INVALID_ARGS;
    }
    if ((arr->length + 1) > MAX_SIZE || loc < 0 || loc >= MAX_SIZE) {
        return ERR_OUT_OF_RANGE;
    }
    for (int i = arr->length; i > loc; i--) {
        arr->data[i] = arr->data[i - 1];
    }
    arr->data[loc] = entry;
    (arr->length)++;
    return 0;
}

static inline int array_delete(array *arr, int loc, data_t *entry)
{
    if (!arr || !entry) {
        return ERR_INVALID_ARGS;
    }
    if (loc < 0 || loc >= arr->length) {
        return ERR_OUT_OF_RANGE;
    }

    *entry = arr->data[loc];
    for (int i = loc; i < (arr->length) - 1; i++) {
        arr->data[i] = arr->data[i + 1];
    }
    (arr->length)--;
    return 0;
}

static inline int array_update(array *arr, int loc, data_t entry)
{
    if (!arr) {
        return ERR_INVALID_ARGS;
    }
    if (!arr->length) {
        return ERR_EMPTY_ARRAY;
    }
    if (loc < 0 || loc >= arr->length) {
        return ERR_OUT_OF_RANGE;
    }
    arr->data[loc] = entry;
    return 0;
}

static inline int array_reverse(array *arr)
{
    if (!arr) {
        return ERR_INVALID_ARGS;
    }

    int i, j, tmp;
    for (i = 0, j = (arr->length - 1); j - i > 0; i++, j--) {
        if (arr->data[i] == arr->data[j]) {
            tmp = arr->data[i];
            arr->data[i] = arr->data[j];
            arr->data[j] = tmp;
        } else {
            arr->data[i] ^= arr->data[j];
            arr->data[j] ^= arr->data[i];
            arr->data[i] ^= arr->data[j];
        }
    }

    return 0;
}

测试代码:

#include <stdio.h>
#include <unistd.h>
#include "array.h"

int main(int argc, char **argv) {
    int ret = 0, i;
    array list;
    array_init(&list);

    /* test is_empty_array */
    printf("Empty array: %d\n", is_array_empty(&list));
    list.data[0] = 2400;
    list.length += 1;
    printf("Empty array: %d\n", is_array_empty(&list));
    /* end test is_empty_array */

    /* test push */
    for (i = list.length; i < MAX_SIZE; i++) {
        if ((ret = array_push(&list, i))) {
            printf("Err: %d\n", ret);
            printf("length: %d\n", list.length);
            printf("Entry: %d\n", i);
        }
    }

    show_array(&list);

    if ((ret = array_push(&list, 20))) {
        printf("Err: %d\n", ret);
        printf("length: %d\n", list.length);
        printf("Entry: %d\n", 20);
    }
    /* end test push */

    /* test insert */
    array_init(&list);
    for (i = list.length; i < MAX_SIZE-1; i++) {
        if ((ret = array_push(&list, i))) {
            printf("Err: %d\n", ret);
            printf("length: %d\n", list.length);
            printf("Entry: %d\n", i);
        }
    }
    array_insert(&list, 3, 5);
    show_array(&list);

    array_insert(&list, 3, 6);
    show_array(&list);

    array_init(&list);
    for (i = list.length; i < MAX_SIZE-1; i++) {
        if ((ret = array_push(&list, i))) {
            printf("Err: %d\n", ret);
            printf("length: %d\n", list.length);
            printf("Entry: %d\n", i);
        }
    }
    array_insert(&list, MAX_SIZE, 5);
    show_array(&list);
    /* end test insert */

    /* test locate_entry */
    array_init(&list);
    for (i = list.length; i < MAX_SIZE; i++) {
        if ((ret = array_push(&list, i))) {
            printf("Err: %d\n", ret);
            printf("length: %d\n", list.length);
            printf("Entry: %d\n", i);
        }
    }
    printf("location of 5 is: %d\n", locate_entry(&list, 5));
    printf("location of 10 is: %d\n", locate_entry(&list, 10));
    printf("location of 100 is: %d\n", locate_entry(&list, 100));
    printf("location of -100 is: %d\n", locate_entry(&list, -100));
    /* end test locate_entry */

    /* test len*/
    array_init(&list);
    for (i = 0; i < 38; i++) {
        if ((ret = array_push(&list, i))) {
            printf("Err: %d\n", ret);
            printf("length: %d\n", list.length);
            printf("Entry: %d\n", i);
        }
    }
    printf("len : %d\n", len(&list));
    /* end test len */

    /* test pop */
    array_init(&list);
    int j = 0;
    for (i = 0; i < 26; i++) {
        array_push(&list, j);
        j += 2;
    }
    show_array(&list);
    for (i = 0; i < 27; i++) {
        array_pop(&list, &j);
        printf("Pop Entry: %d\n", j);
        show_array(&list);
    }
    /* end test pop */

    /* test delete*/
    array_init(&list);
    for (i = 0; i < 26; i++) {
        array_push(&list, j);
        j += 2;
    }
    show_array(&list);
    for (i = 0; i < 27; i++) {
        if (array_delete(&list, i, &j) != 0) {
            printf("Delete %dth entry error!\n", i);
            break;
        }
        printf("Deleted Entry: %d\n", j);
        show_array(&list);
    }
    /* end test delete */

    /* test update*/
    array_init(&list);
    for (i = 0, j = 0; i < 26; i++) {
        array_push(&list, j);
        j += 2;
    }
    show_array(&list);
    for (i = 0; i < 28; i++) {
        if (array_update(&list, i, 99) != 0) {
            printf("Update %dth entry error!\n", i);
            break;
        }
        printf("Updated %dth Entry: %d\n", i, j);
        show_array(&list);
    }
    /* end test update */

    /* test reverse*/
    array_init(&list);
    for (i = 0; i < MAX_SIZE; i++) {
        array_push(&list, i);
    }
    show_array(&list);
    array_reverse(&list);
    show_array(&list);
    /* end test reverse */
}

在fedora下使用搜狗拼音输入法

Linux下的拼音输入法实在是不敢恭维,还好有人把搜狗拼音输入法制作成了RPM包.安装此rpm包就可以在Linux下面使用搜狗拼音输入法及其字库了.

第一步,下载RPM包.

百度网盘地址:http://pan.baidu.com/s/1bpblFoN

第二步,安装RPM包.

    $sudo yum install  fcitx-sogoupinyin-0.0.4-1.fc20.x86_64.rpm  //注意输入正确的路径

第三步,卸载ibus.

    $sudo yum remove ibus

第四步,设置fcitx开机自动启动.

    $sudo yum install gnome-tweak-tool

    $gnome-tweak-tool  //在开机启动一项中添加fcitx即可

第五步,重启gnome

    $gnome-session-quit

最后,使用Ctrl+space愉快的玩耍.

阅读原文

使用jQuery Validation插件来验证表单

jQuery Validation是一个用于验证表单的jQuery插件,简单易用,已经包含了16种内置的验证规则.Github上也有更多的验证规则可以使用.这都不是重点,重点是你可以轻松的定制自己的规则.

内置规则:

  • required – Makes the element required.
  • remote – Requests a resource to check the element for validity.
  • minlength – Makes the element require a given minimum length.
  • maxlength – Makes the element require a given maximum length.
  • rangelength – Makes the element require a given value range.
  • min – Makes the element require a given minimum.
  • max – Makes the element require a given maximum.
  • range – Makes the element require a given value range.
  • step – Makes the element require a given step.
  • email – Makes the element require a valid email
  • url – Makes the element require a valid url
  • date – Makes the element require a date.
  • dateISO – Makes the element require an ISO date.
  • number – Makes the element require a decimal number.
  • digits – Makes the element require digits only.
  • equalTo – Requires the element to be the same as another one

内置规则的使用

内置规则的使用非常简单:

首先将该插件的js文件包含进html文件:

    <script src="/static/js/jquery.min.js"></script>
    <script src="/static/js/jquery.validate.min.js"></script>

然后用jQuery选择需要验证的表单,执行validate()函数即可:

    <script>
    $("#form_id").validate();
    </script>

jQuery Validation官网上的例子:

    <!DOCTYPE HTML>
    <html>
    <head>
    <script src="jquery.min.js"></script>
    </head>
    <body>
    <form class="cmxform" id="commentForm" method="get" action="">
    <fieldset>
    <legend>Please provide your name, email address (won't be published) and a comment</legend>
    <p>
    <label for="cname">Name (required, at least 2 characters)</label>
    <input id="cname" name="name" minlength="2" type="text" required>
    </p>
    <p>
    <label for="cemail">E-Mail (required)</label>
    <input id="cemail" type="email" name="email" required>
    </p>
    <p>
    <label for="curl">URL (optional)</label>
    <input id="curl" type="url" name="url">
    </p>
    <p>
    <label for="ccomment">Your comment (required)</label>
    <textarea id="ccomment" name="comment" required></textarea>
    </p>
    <p>
    <input class="submit" type="submit" value="Submit">
    </p>
    </fieldset>
    </form>
    <script>
    $("#commentForm").validate();
    </script>
    </body>
    </html>

jQuery Validation会根据表单设置的type和属性自动为他们分配内置的规则,比如email,url,required等.

运行一下看看:

什么都不输入,直接点提交:

空表单

输入错误的Email地址,改正后错误提示自动消失: Email输入错误Email输入正确

添加自定义规则

jQuery Validation最吸引人的feature,它可以轻松的加入自定义的规则:

第一步,在js中调用jQuery.validator.addMethod函数来添加规则,例如添加IP格式检查的规则:

$.validator.addMethod( "ipv4", function( value, element ) {return this.optional( element ) || /^(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)$/i.test( value );}, "Invalid IP v4 address." ); //自定义其他规则只需要替换规则名"ipv4",正则表达式//之间的内容,以及出错后显示的字符串"Invalid IP v4 address."即可.

第二步,把规则应用到指定的表单项,即在执行$(“#form_id”).validate()函数的时候加入rules参数:

    23 $("#ip_form").validate({
    24     rules: {
    25         ip: {
    26             required: true,
    27             ipv4: true,
    28         },
    29         netmask: {
    30             required: true,
    31             ipv4: true,
    32         },
    33         gateway: {
    34             required: true,
    35             ipv4: true,
    36         },
    37     },
    38 }); //其中ip,netmask,gateway为表单项的name属性.required和ipv4是规则名.

生效后的样子,可以添加如下css来修改错误信息的样式:

    label.error {
        margin-left: 10px;
        padding-left: 5px;
        padding-right: 5px;
        color: #E2303A;
        font-style: italic;
        font: Helvetica Neue, 12px, #E2303A;
        border: 1px solid #F2A9AE;
    }

ipv4规则生效

使用json提交数据

表单验证通过后,提交动作默认是使用form本身的提交动作,即指定form的action和method属性:

    method="get" action=""

可以在validate()函数中添加submitHandler参数来指定点击提交后执行的函数,我们可以在该函数中使用$.json来提交数据:

    23 $("#ip_form").validate({
    24     rules: {
    25         ip: {
    26             required: true,
    27             ipv4: true,
    28         },
    29         netmask: {
    30             required: true,
    31             ipv4: true,
    32         },
    33         gateway: {
    34             required: true,
    35             ipv4: true,
    36         },
    37         dns: {
    38             dns: true,
    39         }
    40     },
    41     submitHandler: function(form) {
    42         ip_ok();
    43         $("#ip-conf").modal('hide');
    44     }
    45 });

阅读原文

迁移宁强在线步骤

配置ssh

#mkdir /root/.ssh && cp id_rsa* /root/.ssh/

下载配置脚本

#git clone git@git.oschina.net:wangsen/auto_conf_fc.git

安装基础软件

#cd auto_conf_fc && bash -x ./conf.sh

安装web软件

#yum install net-tools httpd python-django                                   
#yum install python python-pip  python-devel  python-wsgi  mod_wsgi   mariadb-server python-mysql mariadb-devel.x86_64 MySQL-python python-html5lib

克隆网站代码

#cd /var/www/html && git clone git@git.oschina.net:wangsen/TownInfo-.git
#mvTownInfo- nqys
#cd nqys && cp httpd.conf /etc/httpd/conf/httpd.conf

安装pip

#cd ~/auto_conf_fc/nqzx/ && tar xzvf pip-1.5.6.tar.gz && cd pip-1.5.6 && python get-pip.py

安装Python包

# cds && pip install -r ../requirements.txt

启动数据库

#vim /etc/my.cnf
# ## character_set_server=utf8
# systemctl enable mariadb
# systemctl restart mariadb
# mysqladmin -u root password
# mysqladmin -u root -p create nqysdb
# mysql -u root -p
##GRANT ALL PRIVILEGES ON nqysdb.* TO 'django_user'@'localhost' IDENTIFIED BY 'passw0rd'; 

替换django和registration

# cd /usr/lib/python2.7/site-packages && rm -rf django
# git clone git@git.oschina.net:wangsen/django.git
# git clone git@git.oschina.net:wangsen/registration.git

解决头像上传问题

# cd ~/auto_conf_fc/nqzx && tar xzvf Imaging-1.1.7.tar.gz
# yum install zlib zlib-devel libjpeg libjpeg-devel freetype freetype-devel
# cd Imaging-1.1.7 && vim setup.py
修改setup.py:
TCL_ROOT = "/usr/lib64/"
JPEG_ROOT = "/usr/lib64/"
ZLIB_ROOT = "/usr/lib64/"
TIFF_ROOT = "/usr/lib64/"
FREETYPE_ROOT = "/usr/lib64/"
LCMS_ROOT = "/usr/lib64/"
# pip uninstall pillow PIL
# python setup.py install

导入备份数据库

  1. 备份数据库
# mysqldump -u root -p nqysdb > nqzx.db
  1. 导入数据库
# mysql -u root -p nqysdb < nqzx.db

启动web服务

# cds && ../manage.py collectstatic
# ../manage.py syncdb
# service httpd restart

JQuery 摘要

选择符与遍历

  1. $(): $函数接受css选择符作为参数,充当一个工厂函数,返回对应元素的JQuery对象。

  2. 3种基本的选择符:

    • 标签名 $(‘p’)
    • ID $(‘#myid’)
    • 类 $(‘.myClass’)
  3. 子元素组合符>: $(‘#myid > li’)选择id为myid的元素的所有列表项(li).

  4. 否定式伪类: $(‘#myid li:not(.myClass)’)选择id为myid的元素中不属于myClass类的所有列表项(li).

  5. 属性选择符$(‘img[alt]’): 选择所有带有alt属性的img元素.

  6. 属性选择符+类正则匹配:

    • $(‘a[href^=”mailto:”]’): 选择所有URL以mailto:开头的超链接.
    • $(‘a[href$=”.pdf”]’): 选择所有URL以.pdf结尾的超链接.
    • $(‘a[href*=”rose”]’): 选择所有URL中包含rose的超链接.
  7. 自定义选择符:

    • $(‘li:eq(1)’): 选择第二个列表项
    • $(‘li:odd’): 选择奇数的列表项
    • $(‘li:even’): 选择偶数的列表项
    • $(‘li:nth-child(odd)’): 选择从父元素的第一个元素开始计算的所有奇数列表项
    • $(‘li:contain(string)’): 选择包含string的列表项
  8. 表单选择符:

    • :input
    • :button
    • :checked
    • :selected
    • :enabled
    • :disabled
  9. filter: 过滤器,可接选择器和lambda函数

    • $(‘li’).filter(‘:even’)
    • $(‘li’).filter(function(){return this.hostname && this.hostname!=location.hostname})
  10. next,nextALL,prev,prevAll,andSelf,siblings:选择所选元素的下一个元素等

  11. 连缀(chaining)

    • $(‘tr:contains(Henry)’).parent().find(‘td:eq(1)’).addClass(‘myClass’).end().find(‘td:eq(2)’).addClass(‘myClass’);
  12. $(‘#myId’).get(0) 等价于 $(‘myId’)[0]

事件

  1. window.onload vs $(document).ready

    • window.onload 是在页面的全部元素都下载好了之后才能执行,而$(document).ready()则是在DOM树准备好就可以执行了。
    • window.onload只能指定一个函数,而$(document).ready()指定的所有函数都会按顺序执行。
    • $(document).ready(func)可以简写为$(func);
  2. bind(event, func)函数可以为DOM节点绑定事件,以及事件发生时所执行的函数。event可以是:

    • blur
    • change
    • click
    • dbclick
    • error
    • focus
    • keydown
    • keypress
    • keyup
    • load
    • mousedown
    • mousemove
    • mouseout
    • mouseover
    • mouseup
    • resize
    • scroll
    • select
    • submit
    • unload
  3. toggle(func1, func2): 单击时轮流执行func1和func2

  4. toggleClass(“someclass”): 单击时轮流添加或删除someclass.

  5. 事件捕获和事件冒泡:事件捕获是从父节点开始将事件传递给子节点,而事件冒泡则正好相反。JQuery采取事件冒泡的策略。

  6. 事件对象:事件发生时执行的函数可以把事件对象作为参数。

    • event.target属性:保存发生事件的目标元素
    • event.stopPropagation(): 阻止事件继续冒泡
    • event.preventDefault(): 阻止事件的默认操作
  7. $(event.target).is(): 接收一个选择符表达式作为参数,并验证JQuery对象是否满足它。

  8. unbind(): 移除事件处理

  9. 事件命名空间:bind(‘click.sometag’, func) 可以在unbind的时候只解绑指定名字的事件。

  10. trigger(): 使用javascript去触发某个事件

效果

  1. .css(): 参数可以是(“attr”, “value”),也可以是({“attr”: “value”, “attr”: “value”}),修改JQuery对象的css
  2. .hide(): 将JQuery对象的内联css属性”display”设置为”none”
  3. .show(): 将JQuery对象的内联css属性”display”恢复成hide之前的值。hide和show可以传入速度作为参数:”slow”, “normal”, “fast”
  4. fadeIn()和fadeOut(): 淡入和淡出,可传入速度参数。
  5. slideDown()和slideUp(): 滑下和滑上,可传入速度参数。
  6. toggle(): 相当于轮流执行show()和hide()方法,可传入速度参数。
  7. slideToggle(): 相当于轮流执行slideUp()和slideDown(),可传入速度参数。
  8. animate(): 自定义动画。有两种传入参数的方式:
    • ({“attr”: “value”, “attr”, “value}, speed, easing, func):第一个参数是css属性,第二个是速度,第三个是缓动,第四个是动画完成后的回调函数。
    • ({“attr”: “value”, “attr”: “value”}, {duration: “value”, easing: “value”, complete: functiong(){}, queue: true…})
  9. outerWidth(true): 返回width+padding+border+margin的值
  10. outerWidth(), outerWidth(false): 返回width+padding+border的值
  11. innerWidth(): 返回width+padding的值
  12. outerHeight(), innerHeight()与outerWidth(), innerWidth()类似
  13. animate()中指定多个css属性变化可以让动画并发,而用多个效果方法如animate,fadeIn等连缀则可以让动画排队显示。设置queue参数为false则可以让该动画不用排队。
  14. css()方法不属于效果方法,不能排队,但可以用queue()方法将其加入队列,例如:
    • .fadeTo().queue(function(next){$(xxx).css(); next();})
  15. JQuery为每个效果方法都提供了回调函数,可以用来让多个JQuery对象的动画排队执行。

操作DOM

AJAX

使用gerrit作为代码评审工具

需求描述

其实作为项目代码的maintainer,一直习惯于mailing list + git的代码评审及管理,无奈公司主推敏捷+devops,老板让改用gerrit。硬着头皮切换到gerrit,在这里记录下安装配置的过程及踩过的许多坑,以便网友们以后配置gerrit留作参考。

需求其实很简单,我们项目一直使用公司内部一个类似于github的代码托管网站来托管项目代码,使用邮件列表来评审代码。代码通过评审通过后,我再将patch push到代码托管服务器上去。整个开发流程如下图所示:

目前开发流程

现在需要切换到gerrit来作为代码评审工具,以便于能够和jenkins集成,搭建一个集开发、构建、测试、部署为一体的devops系统,结构如下图所示。本文只关注gerrit的搭建。

CI

Gerrit简介

安装步骤

1 . 安装Java.

网上有很多安装java的博客和文章,因此在这里不再赘述,可以参考下面这篇文章:

Linux下安装java

2 . 给Gerrit单独创建一个账户

#useradd gerrit
#passwd gerrit
#su gerrit

3 . 下载gerrit

gerrit是在google上托管的项目,翻墙下载比较麻烦,可以在这里下载2.11版本的gerrit:

百度网盘下载Gerrit

将网盘中的两个文件gerrit-2.11.war以及bcpkix-jdk15on-151.jar下载到gerrit用户的家目录/home/gerrit下。

4 . 按下面的步骤安装gerrit,其中的问题大部分可以敲回车选择默认设置。


[gerrit@linux ~]$ java -jar ./gerrit-2.11.war init -d gerrit 
    
//-d指定gerrit的安装目录

Using secure store: com.google.gerrit.server.securestore.DefaultSecureStore

*** Gerrit Code Review 2.11
*** 

Create '/home/gerrit/gerrit'   [Y/n]? y

*** Git Repositories
*** 

Location of Git repositories   [git]:  

*** SQL Database
*** 

Database server type           [h2]: 

*** Index
*** 

Type                           [LUCENE/?]: 

*** User Authentication
*** 

Authentication method          [OPENID/?]: http    //为了不依赖于openid我们这里要设置成http
Get username from custom HTTP header [y/N]? y
Username HTTP header           [SM_USER]: 
SSO logout URL                 : 

*** Review Labels
*** 

Install Verified label         [y/N]? 

*** Email Delivery
*** 

SMTP server hostname           [localhost]: 
SMTP server port               [(default)]: 
SMTP encryption                [NONE/?]: 
SMTP username                  : 

*** Container Process
*** 

Run as                         [gerrit]: 
Java runtime                   [/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.45-38.b14.fc21.x86_64/jre]: 
Copy gerrit-2.11.war to /home/gerrit/gerrit/bin/gerrit.war [Y/n]? 
Copying gerrit-2.11.war to /home/gerrit/gerrit/bin/gerrit.war

*** SSH Daemon
*** 

Listen on address              [*]: 
Listen on port                 [29418]: 

Gerrit Code Review is not shipped with Bouncy Castle Crypto SSL v151
  If available, Gerrit can take advantage of features
  in the library, but will also function without it.
Download and install it now [Y/n]? n  //选择n,因为这个文件已经下载好了,安装完手动拷贝进去就行
Generating SSH host key ... rsa(simple)... done

*** HTTP Daemon
*** 

Behind reverse proxy           [y/N]? y  //需要配置反向代理来满足http认证
Proxy uses SSL (https://)      [y/N]? N
Subdirectory on proxy server   [/]: 
Listen on address              [*]: 
Listen on port                 [8081]: 
Canonical URL                  [http://null/]: 

*** Plugins
*** 

Installing plugins.
Install plugin download-commands version v2.11 [y/N]? 
Install plugin reviewnotes version v2.11 [y/N]? 
Install plugin singleusergroup version v2.11 [y/N]? 
Install plugin replication version v2.11 [y/N]? 
Install plugin commit-message-length-validator version v2.11 [y/N]? 
Initializing plugins.
No plugins found with init steps.

Initialized /home/gerrit/gerrit
Executing /home/gerrit/gerrit/bin/gerrit.sh start
Starting Gerrit Code Review: OK

安装完成后需要将下载好的bcpkix-jdk15on-151.jar文件拷贝到安装目录中去:

cp bcpkix-jdk15on-151.jar /home/gerrit/gerrit/lib/bcpkix-jdk15on-151.jar

5 . 配置Apache反向代理

修改httpd的配置文件,在其末尾加入:

<VirtualHost *>
  ServerName gerrit.example.com

  ProxyRequests Off
  ProxyVia Off
  ProxyPreserveHost On

  <Proxy *>
    Order deny,allow
    Allow from all
  </Proxy>
  <Location /login/>
    AuthType Basic
    AuthName "Gerrit Code Review"
    AuthBasicProvider file
    AuthUserFile /home/gerrit/gerrit/etc/passwords
    Require valid-user
  </Location>
  ProxyPass / http://9.181.129.109:8081/
</VirtualHost>

需要将IP地址替换成你自己的IP.修改/etc/hosts,加入gerrit.example.com的解析:

9.181.129.109	gerrit.example.com

这里也需要替换IP地址。

6 . 创建管理员账户,Gerrit把第一个被创建的用户当做是管理员账户:

$htpassword /home/gerrit/gerrit/etc/passwords $username

$username替换成你要设置的用户名。

7 . 重启服务

$./gerrit/bin/gerrit.sh restart
$sudo service httpd restart

问题总结

1 . Could not open password file.

症状:web浏览器提示服务器端错误,查看httpd日志文件/var/log/httpd/error_log,发现如下错误信息:

Permission denied:......Could not open password file: /home/gerrit/gerrit/etc/passwords

原因:该问题是由于passwords文件的路径上权限设置阻挡了httpd的访问。 解决方法:将/home/gerrit目录及passwords文件的权限设置为755

$chmod 755 /home/gerrit
$chmod 755 /home/gerrit/gerrit/etc/passwords

2 . Proxy Error

症状:输入用户名和密码登陆后,web浏览器提示代理错误。

Proxy Error

The proxy server received an invalid response from an upstream server.
The proxy server could not handle the request GET /login/.

Reason: DNS lookup failure for: 9.181.129.109:8081login

原因:最后发现是httpd的配置文件中在ProxyPass一行的IP地址后少写了一个/

ProxyPass / http://9.181.129.109:8081
--->
ProxyPass / http://9.181.129.109:8081/

参考文献

1 . Gerrit官方文档 2 . Gerrit简易安装入门

apache配置https服务

1、创建自己签名的证书

         #创建CA签名的证书,需要用到openssl
         yum install openssl    
         #创建key   
         openssl genrsa -des3 -out server.key 1024     
         #创建csr(证书签发请求) 
         openssl req -new -key server.key -out server.csr      
         #生成自己签名的证书
         openssl x509 -req  -in server.csr  -signkey server.key -out server.crt       
         #安装证书
         cp  server.crt /etc/ssl/certs
         cp  server.key /etc/ssl/private

2、编辑ssl配置文件

         vim /etc/httpd/conf.d/ssl.conf
         SSLEngine on
         SSLCertificateFile    /etc/ssl/certs/server.crt
         SSLCertificateKeyFile /etc/ssl/private/server.key

3、重启apache服务

        #systemctl restart httpd

mysql数据库及django用户名启用中文的方法

mysql数据库启用中文

在mysql的配置文件/etc/my.cnf的[mysqld]下加入

character_set_server=utf8

Django启用中文用户名

Django默认只能以字母、数字、下划线组成用户名,修改检验用户名的正则表达式可以绕过这一规则:

/usr/lib/python2.7/site-packages/django/contrib/auth/models.py:

class AbstractUser(AbstractBaseUser, PermissionsMixin):
...
validators.RegexValidator(re.compile('^[\w.@+-]+$'), _('Enter a valid username.'), 'invalid')
...

把正则表达式从^[\w.@+-]+$ 改为 ^[\S.@+-]+$即可

基于Jenkins和Koji的代码管理及构建系统

<h1>Summary</h1>

Currently, we KVM team are maintaining the code repository of the virtualization software(such as QEMU, Libvirt, Kimchi, Linux, etc.) for PowerKVM development team and delivering corresponding RPM packages. We collect patches from mailing list and build update RPM packages every workday. The work load is extremely heavy if we manually download the patches from mailing list, apply them to the repository, and build RPM packages covering all the supported platforms.

This environment, based on Jenkins and Koji, can automatically manage the code(apply the patches from mailing lists to the appropriate repository and branch) and build the required RPM packages covering all supported platforms nightly or triggered manually. By using this automation environment, developers can easily get the updated RPM packages just by sending the patches to the given e-mail address or mailing list. Furthermore, the time cost for daily routine work is dramatically reduced and the quality of the packages will be improved by eliminating human errors. This environment also can be used to other projects by making few changes to configure files.

<h1>Challenges</h1>

In PowerKVM development, we have to ship all the significant bug fixes frequently, usually a couple days. All the fix patches of QEMU, Libvirt, Linux, Kimchi will be sent to a certain mailing list. After the code cut-off day, we download all the patches from the mailing list and manually apply them to different git repositories accordingly. Then copy all the git repositories to different platform(RHEL,Fedora,etc) and do build. Last step, copy all the output RPM packages to different RPM repositories accordingly. Sometimes, we can not deliver the packages on time because the workload is extremely heavy. Furthermore, errors occur frequently such as some patches may get lost during the process due to lack of time. In order to solve these problems, we decided to make use of Jenkins and Koji to establish the CI environment.

During the construction of this environment, we have to resolve the following problems:

  • Define the rules of sending patches for developers in order to make the CI know where the patches should go.
  • Define the regular expression to distinguish patches from other e-mails.
  • How to handle exceptions.

<h1>Experience</h1>

<1>. Tools and Framework Overview

In order to implement this automation environment, several tools are needed:

  • Jenkins: Jenkins is a software that monitors executions of repeated jobs. This automation environment use Jenkins as the user interface and the manager of all the underlying jobs.
  • Koji: Koji can build RPMs packages for multiple platforms. This automation environment make use of Koji to build RPMs for all supported platform.
  • Python scripts: implement the function that collect patches from mailing list, organize and archive these patches.
  • Shell scripts: Apply the patches to the appropriate git repositories, process build and release the output RPM packages via web server.
  • Apache HTTP server: Release the RPMs via web page. Below is the chart about the framework overview of this automation environment and the relationship among these tools.

Framework

<2>. User Interface

The automation environment runs nightly. However, if the developer want to get the RPM packages immediately, he or she can simply click the start button of Jenkins. See below chart.

User Interface

<3>. Execution flow

After the developers click the first run button, jenkins will call python scripts to collect emails matching certain regular expression(We advise developers send patches to mailing list in fixed forms, such as [branch_name PATCH repo_name]xxx) and archive all the patches by date. Then, Jenkins will call shell scripts to apply these patches to the appropriate git repositories. After that, Koji will be noticed to build the RPM packages of all the supported platforms. Finally, Jenkins will call the shell scripts to copy all the RPM packages to certain directory of Apache web server.

<4>. Handle exception

The automation environment will send report containing how many patches received and where the RPM packages are published to the mailing list if all the jobs are successfully executed. If a certain job failed, it also sends report to the mailing list or email boxes configured in the configuration file. Different job has different error report message. For example, when the job who collects patches from mailing list fails, it will send out the failed patches’ name, but when the job who builds RPM packages fails, it will send out the build log.

<h1>Benefit</h1>

For the repositories maintainers, this automation environment can save much time costs for their daily routing work. What they need to do is just check reports via email clients if this automation environment start to work. The time costs dramatically reduce from several hours to a few minutes.

For the developers, they can get the RPM packages by themselves whenever they want to build one, instead of asking the maintainers spend much time to build for them. What they need to do is simply click a button.

Furthermore, the quality of the RPM packages will be improved because this automation environment can eliminate human errors effectively.

<h1>Next Steps and Recommendations</h1>

Currently, this automation environment is just used for code management and RPM packages build. Since it can save much time cost for developers and repository maintainers, we are planing to involve test cases. We can make use of automation test technology and integrate the test cases into this automation environment. After that, test engineers will be set free from burdensome test work.

发行版制作及Anaconda基础

Anaconda是用于Fedora, RHEL等Linux发行版的安装程序,可以实现通过LiveCD,PXE,NFS等方式安装Linux系统以及利用kickstart文件实现无人值守的无交互安装。

发行版制作(Fedoran系统环境)

  1. 选择所需的软件包。

制作自己的发行版首先要确定你的发行版需要安装哪些软件。决定之后需要把这些软件相应的RPM包拷贝到某个目录,然后在这个目录下执行createrepo命令来创建RPM软件源。

  1. 创建ks文件。

ks文件用于指定制作发行版时用到的软件源及软件包,具体语法可参考 kickstart语法,下面是一个简单的例子:

编译及安装

  • 获取源码:git clone git://git.fedorahosted.org/git/anaconda.git
  • 安装依赖包: sudo yum install libtool $(grep ^BuildRequires: anaconda.spec.in | awk ‘{print $2}’)
  • 安装、配置transifex:sudo yum install transifex-client;tx init /tmp
  • ./autogen.sh && ./configure && make po-pull && make

源码目录结构

  1. 接口:pyanaconda/ui/

    • gui/:图形界面接口实现代码。
    • tui/:字符界面及命令行界面实现代码。
    • _init_.py 及 common.py:定义了gui和tui通用的基类(base class)
    • communication.py:负责UI中类的通信。
  2. 自定义组件:widgets/

    • data/:存放时区地图组件的图片。
    • glade/及python/:让用户接口构建器知道组件的存在及实现python的自省。
    • src/:实现各组件。
  3. 分区:python-blivet包

  4. Bootloader: pyanaconda/bootloader.py

  5. 各个步骤的配置:

    • pyanaconda/desktop.py
    • pyanaconda/keyboard.py
    • pyanaconda/localization.py
    • pyanaconda/network.py
    • pyanaconda/ntp.py
    • pyanaconda/timezone.py
    • pyanaconda/users.py
  6. 安装软件包:

    • pyanaconda/packaging/
    • scripts/anaconda-yum
  7. 安装类: 不同的发行版可以定义不同的安装类。

    • pyanaconda/installclass.py
    • pyanaconda/installclasses/
    • pyanaconda/product.py
  8. 无人值守安装:pyanaconda/kickstart.py

  9. liveCD:

    • data/icons/
    • data/liveinst/
  10. 错误处理:

    • pyanaconda/errors.py
    • pyanaconda/exception.py
  11. 安装控制库

    • pyanaconda/install.py:控制安装步骤。
    • pyanaconda/progress.py:控制进度条。
    • pyanaconda/queue.py:控制通信队列。
    • pyanaconda/threads.py:多线程支持。
  12. 库:提供一些工具如获得用户位置,安装日志等。

    • pyanaconda/init.py
    • pyanaconda/addons.py
    • pyanaconda/anaconda_log.py
    • pyanaconda/anaconda_optparse.py
    • pyanaconda/constants.py
    • pyanaconda/flags.py
    • pyanaconda/geoloc.py
    • pyanaconda/i18n.py
    • pyanaconda/image.py
    • pyanaconda/indexed_dict.py
    • pyanaconda/isys/
    • pyanaconda/iutil.py
    • pyanaconda/nm.py
    • pyanaconda/safe_dbus.py
    • pyanaconda/simpleconfig.py
    • pyanaconda/sitecustoimze.py
  13. 主程序anaconda:由systemd在系统启动后调用,设置环境、VNC等。

  14. 启动

    • data/systemd/
    • dracut/
  15. 内存监控

    • scripts/anaconda-cleanup:监控安装过程中的内存状态,并记录到/tmp/memory.dat文件中。
    • scripts/instperf及scripts/instperf.p:利用memory.dat文件生成相应的图表。
  16. 升级工具

    • scripts/makebumpver
    • scripts/makeupdates

QEMU 2: 参数解析

#一、使用gdb分析QEMU代码#

使用gdb不仅可以很好地调试代码,也可以利用它来动态地分析代码。使用gdb调试QEMU需要做一些准备工作:

1, 编译QEMU时需要在执行configure脚本时的参数中加入–enable-debug。

2, 从QEMU官方网站上下载一个精简的镜像——linux-0.2.img。linux-0.2.img只有8MB大小,启动后包含一些常用的shell命令,用于QEMU的测试。

$wget http://wiki.qemu.org/download/linux-0.2.img.bz2
$bzip2 -d ./linux-0.2.img.bz2

3, 启动gdb调试QEMU:

gdb --args qemu-system-x86_64 -enable-kvm -m 4096 -smp 4 linux-0.2.img

-smp指定处理器个数。

#二、参数解析用到的数据结构#

QEMU系统模拟的主函数位于vl.c文件,无论是qemu-system-x86_64还是qemu-system-ppc64,都是从vl.c中的main函数开始执行。下面先介绍main函数涉及到的一些数据结构。

###QEMU链表

QEMU的链表在include/qemu/queue.h文件中定义,分为四种类型:

  • 单链表(singly-linked list):单链表适用于大的数据集,并且很少有删除节点或者移动节点的操作,也适用于实现后进先出的队列。
  • 链表(list):即双向链表,除了头节点之外每个节点都会同时指向前一个节点和后一个节点。
  • 简单队列(simple queue):简单队列类似于单链表,只是多了一个指向链表尾的一个表头,插入节点的时候不仅可以像单链表那样将其插入表头或者某节点之后,还可以插入到链表尾。
  • 尾队列(tail queue):类似于简单队列,但节点之间是双向指向的。

这里不一一介绍各种链表的用法,只通过NotifierList的定义来说明QEMU链表(list)的用法。在main函数的开头定义的DisplayState结构体使用到了NotifiereList,NotifierList就用到了链表。

a. 表头及节点的定义

定义表头需要用到QLIST_HEAD,定义如下:

 86 #define QLIST_HEAD(name, type)                                          \
 87 struct name {                                                           \
 88         struct type *lh_first;  /* first element */                     \
 89 }

NotifierList就采用了QLIST_HEAD来定义表头:

 27 typedef struct NotifierList
 28 {
 29     QLIST_HEAD(, Notifier) notifiers;
 30 } NotifierList;

定义节点需要用到QLIST_ENTRY,定义如下:

 94 #define QLIST_ENTRY(type)                                               \
 95 struct {                                                                \
 96         struct type *le_next;   /* next element */                      \
 97         struct type **le_prev;  /* address of previous next element */  \
 98 }

Notifier的节点定义如下:

 21 struct Notifier
 22 {
 23     void (*notify)(Notifier *notifier, void *data);
 24     QLIST_ENTRY(Notifier) node;
 25 };

b. 初始化表头

初始化表头用到QLIST_INIT:

103 #define QLIST_INIT(head) do {                                           \
104         (head)->lh_first = NULL;                                        \
105 } while (/*CONSTCOND*/0)

初始化NotifierList就可以这样进行:

 19 void notifier_list_init(NotifierList *list)
 20 {
 21     QLIST_INIT(&list->notifiers);
 22 }

c. 在表头插入节点

将节点插入到表头使用QLIST_INSERT_HEAD:

122 #define QLIST_INSERT_HEAD(head, elm, field) do {                        \
123         if (((elm)->field.le_next = (head)->lh_first) != NULL)          \
124                 (head)->lh_first->field.le_prev = &(elm)->field.le_next;\
125         (head)->lh_first = (elm);                                       \
126         (elm)->field.le_prev = &(head)->lh_first;                       \
127 } while (/*CONSTCOND*/0)

插入Notifier到NotifierList:

 24 void notifier_list_add(NotifierList *list, Notifier *notifier)
 25 {
 26     QLIST_INSERT_HEAD(&list->notifiers, notifier, node);
 27 }

d. 遍历节点

遍历节点使用QLIST_FOREACH或者QLIST_FOREACH_SAFE,QLIST_FOREACH_SAFE是为了防止遍历过程中删除了节点,从而导致le_next被释放掉,中断了遍历。

147 #define QLIST_FOREACH(var, head, field)                                 \
148         for ((var) = ((head)->lh_first);                                \
149                 (var);                                                  \
150                 (var) = ((var)->field.le_next))
151 
152 #define QLIST_FOREACH_SAFE(var, head, field, next_var)                  \
153         for ((var) = ((head)->lh_first);                                \
154                 (var) && ((next_var) = ((var)->field.le_next), 1);      \
155                 (var) = (next_var))

NotifierList在执行所有的回调函数时就用到了QLIST_FOREACH_SAFE:

 34 void notifier_list_notify(NotifierList *list, void *data)
 35 {
 36     Notifier *notifier, *next;
 37 
 38     QLIST_FOREACH_SAFE(notifier, &list->notifiers, node, next) {
 39         notifier->notify(notifier, data);
 40     }
 41 }

###Error和QError

为了方便的处理错误信息,QEMU定义了Error和QError两个数据结构。Error在qobject/qerror.c中定义:

101 struct Error
102 {
103     char *msg;
104     ErrorClass err_class;
105 };

包含了错误消息字符串和枚举类型的错误类别。错误类别有下面几个:

 139 typedef enum ErrorClass
 140 {
 141     ERROR_CLASS_GENERIC_ERROR = 0,
 142     ERROR_CLASS_COMMAND_NOT_FOUND = 1,
 143     ERROR_CLASS_DEVICE_ENCRYPTED = 2,
 144     ERROR_CLASS_DEVICE_NOT_ACTIVE = 3,
 145     ERROR_CLASS_DEVICE_NOT_FOUND = 4,
 146     ERROR_CLASS_K_V_M_MISSING_CAP = 5,
 147     ERROR_CLASS_MAX = 6,
 148 } ErrorClass;

QEMU在util/error.c中定义了几个函数来对Error进行操作:

error_set  //根据给定的ErrorClass以及格式化字符串来给Error分配空间并赋值
error_set_errno  //除了error_set的功能外,将指定errno的错误信息追加到格式化字符串的后面
error_copy  //复制Error           
error_is_set   //判断Error是否已经分配并设置
error_get_class  //获取Error的ErrorClass      
error_get_pretty  //获取Error的msg
error_free  //释放Error及msg的空间

另外,QEMU定义了QError来处理更为细致的错误信息:

 22 typedef struct QError { 
 23     QObject_HEAD;
 24     Location loc;
 25     char *err_msg;
 26     ErrorClass err_class;
 27 } QError;

QError可以通过一系列的宏来给err_msg及err_class赋值:

 39 #define QERR_ADD_CLIENT_FAILED \
 40     ERROR_CLASS_GENERIC_ERROR, "Could not add client"
 41 
 42 #define QERR_AMBIGUOUS_PATH \
 43     ERROR_CLASS_GENERIC_ERROR, "Path '%s' does not uniquely identify an object"
 44 
 45 #define QERR_BAD_BUS_FOR_DEVICE \
 46     ERROR_CLASS_GENERIC_ERROR, "Device '%s' can't go on a %s bus"
 47 
 48 #define QERR_BASE_NOT_FOUND \
 49     ERROR_CLASS_GENERIC_ERROR, "Base '%s' not found"
...

Location记录了出错的位置,定义如下:

 20 typedef struct Location {
 21     /* all members are private to qemu-error.c */
 22     enum { LOC_NONE, LOC_CMDLINE, LOC_FILE } kind;
 23     int num;
 24     const void *ptr;
 25     struct Location *prev;
 26 } Location;

###GMainLoop

QEMU使用glib中的GMainLoop来实现IO多路复用,关于GMainLoop可以参考博客GMainLoop的实现原理和代码模型。由于GMainLoop并非QEMU本身的代码,本文就不重复赘述。

#三、QEMUOption、QemuOpt及QEMU参数解析

QEMU定义了QEMUOption来表示执行qemu-system-x86_64等命令时用到的选项。在vl.c中定义如下:

2123 typedef struct QEMUOption {
2124     const char *name;   //选项名,如 -device, name的值就是device
2125     int flags;  //标志位,表示选项是否带参数,可以是0,或者HAS_ARG(值为0x0001)
2126     int index;  //枚举类型的值,如-device,该值就是QEMU_OPTION_device
2127     uint32_t arch_mask;  //  选项支持架构的掩码
2128 } QEMUOption;

vl.c中维护了一个QEMUOption数组qemu_options来存储所有可用的选项,并利用qemu-options-wrapper.h和qemu-options.def来给该数组赋值。赋值语句如下:

2130 static const QEMUOption qemu_options[] = {
2131     { "h", 0, QEMU_OPTION_h, QEMU_ARCH_ALL },
2132 #define QEMU_OPTIONS_GENERATE_OPTIONS
2133 #include "qemu-options-wrapper.h"
2134     { NULL },
2135 };

#define QEMU_OPTIONS_GENERATE_OPTIONS选择qemu-options-wrapper.h的操作,qemu-options-wrapper.h可以进行三种操作:

QEMU_OPTIONS_GENERATE_ENUM: 利用qemu-options.def生成一个枚举值列表,就是上面提到的QEMU_OPTION_device等
QEMU_OPTIONS_GENERATE_HELP: 利用qemu-options.def生成帮助信息并输出到标准输出
QEMU_OPTIONS_GENERATE_OPTIONS: 利用qemu-options.def生成一组选项列表

可以通过下面的方法来展开qemu-options-wrapper.h来查看上述操作的结果,以生成选项为例。

  1. 在qemu-options-wrapper.h第一行写入#define QEMU_OPTIONS_GENERATE_OPTIONS.
  2. 执行命令gcc -E -o options.txt qemu-options-wrapper.h
  3. 查看文件options.txt即可

给qemu_options数组赋值后,QEMU就有了一个所有可用选项的集合。之后在vl.c中main函数的一个for循环根据这个集合开始解析命令行。for循环的框架大致如下:

  1     for(;;) {
  2         if (optind >= argc)
  3             break;
  4         if (argv[optind][0] != '-') {
  5         hda_opts = drive_add(IF_DEFAULT, 0, argv[optind++], HD_OPTS);
  6         } else {
  7             const QEMUOption *popt;
  8 
  9             popt = lookup_opt(argc, argv, &optarg, &optind);
 10             if (!(popt->arch_mask & arch_type)) {
 11                 printf("Option %s not supported for this target\n", popt->name);
 12                 exit(1);
 13             }
 14             switch(popt->index) {
 15             case QEMU_OPTION_M:
 16             ......
 17             case QEMU_OPTION_hda:
 18             ......  
 19             case QEMU_OPTION_watchdog:
 20             ......
 21             default:
 22                 os_parse_cmd_args(popt->index, optarg);
 23             }   
 24         }
 25     }

QEMU会把argv中以’-‘开头的字符串当作选项,然后调用lookup_opt函数到qemu_options数组中查找该选项,如果查找到的选项中flags的值是HAS_ARG,lookup_opt也会将参数字符串赋值给optarg。找到选项和参数之后,QEMU便根据选项中的index枚举值来执行不同的分支。

对于一些开关性质的选项,分支执行时仅仅是把相关的标志位赋值而已,如:

3712             case QEMU_OPTION_old_param:
3713                 old_param = 1;
3714                 break;

也有一些选项没有子选项,分支执行时就直接把optarg的值交给相关变量:

3822             case QEMU_OPTION_qtest:
3823                 qtest_chrdev = optarg;
3824                 break;

对于那些拥有子选项的选项,如”-drive if=none,id=DRIVE-ID”,QEMU的处理会更为复杂一些。它会调用qemu_opts_parse来解析子选项,如realtime选项的解析:

3852             case QEMU_OPTION_realtime:
3853                 opts = qemu_opts_parse(qemu_find_opts("realtime"), optarg, 0);
3854                 if (!opts) {
3855                     exit(1);
3856                 }
3857                 configure_realtime(opts);
3858                 break;

对子选项的解析涉及到4个数据结构:QemuOpt, QemuDesc, QemuOpts, QemuOptsList. 它们的关系如下图所示:

Qemu选项数据结构关系图

QemuOpt存储子选项,每个QemuOpt有一个QemuOptDesc来描述该子选项名字、类型、及帮助信息。两个结构体定义如下:

 32 struct QemuOpt {
 33     const char   *name;  //子选项的名字
 34     const char   *str;  //字符串值
 35 
 36     const QemuOptDesc *desc;  
 37     union {
 38         bool boolean;  //布尔值
 39         uint64_t uint;  //数字或者大小
 40     } value; 
 41 
 42     QemuOpts     *opts;  
 43     QTAILQ_ENTRY(QemuOpt) next;
 44 };

 95 typedef struct QemuOptDesc {
 96     const char *name;
 97     enum QemuOptType type;
 98     const char *help;
 99 } QemuOptDesc;

子选项的类型可以是:

 88 enum QemuOptType {
 89     QEMU_OPT_STRING = 0,   // 字符串
 90     QEMU_OPT_BOOL,       // 取值可以是on或者off
 91     QEMU_OPT_NUMBER,     // 数字
 92     QEMU_OPT_SIZE,       // 大小,可以有K, M, G, T等后缀
 93 };

QEMU维护了一个QemuOptsList*的数组,在util/qemu-config.c中定义:

10 static QemuOptsList *vm_config_groups[32];

在main函数中由qemu_add_opts将各种QemuOptsList写入到数组中:

2944     qemu_add_opts(&qemu_drive_opts);
2945     qemu_add_opts(&qemu_chardev_opts);
2946     qemu_add_opts(&qemu_device_opts); 
2947     qemu_add_opts(&qemu_netdev_opts);
2948     qemu_add_opts(&qemu_net_opts);
2949     qemu_add_opts(&qemu_rtc_opts);
2950     qemu_add_opts(&qemu_global_opts);
2951     qemu_add_opts(&qemu_mon_opts);
2952     qemu_add_opts(&qemu_trace_opts);
2953     qemu_add_opts(&qemu_option_rom_opts);
2954     qemu_add_opts(&qemu_machine_opts);
2955     qemu_add_opts(&qemu_smp_opts);
2956     qemu_add_opts(&qemu_boot_opts);
2957     qemu_add_opts(&qemu_sandbox_opts);
2958     qemu_add_opts(&qemu_add_fd_opts);
2959     qemu_add_opts(&qemu_object_opts);
2960     qemu_add_opts(&qemu_tpmdev_opts);
2961     qemu_add_opts(&qemu_realtime_opts);
2962     qemu_add_opts(&qemu_msg_opts);

每个QemuOptsList存储了大选项所支持的所有小选项,如-realtime大选项QemuOptsList的定义:

 507 static QemuOptsList qemu_realtime_opts = {
 508     .name = "realtime",
 509     .head = QTAILQ_HEAD_INITIALIZER(qemu_realtime_opts.head),
 510     .desc = {
 511         {
 512             .name = "mlock",
 513             .type = QEMU_OPT_BOOL,
 514         },
 515         { /* end of list */ }
 516     },
 517 };

-realtime只支持1个子选项,且值为bool类型,即只能是on或者off。

在调用qemu_opts_parse解析子选项之前,QEMU会调用qemu_find_opts(“realtime”),把QemuOptsList *从qemu_add_opts中找出来,和optarg一起传递给qemu_opts_parse去解析。QEMU可能会多次使用同一个大选项来指定多个相同的设备,在这种情况下,需要用id来区分。QemuOpts结构体就表示同一id下所有的子选项,定义如下:

 46 struct QemuOpts {
 47     char *id;
 48     QemuOptsList *list;
 49     Location loc;
 50     QTAILQ_HEAD(QemuOptHead, QemuOpt) head;
 51     QTAILQ_ENTRY(QemuOpts) next;
 52 };

其中list是同一个大选项下不同id的QemuOpts链表。

QEMU 1: 使用QEMU创建虚拟机

一、QEMU简介#

QEMU是一款开源的模拟器及虚拟机监管器(Virtual Machine Monitor, VMM)。QEMU主要提供两种功能给用户使用。一是作为用户态模拟器,利用动态代码翻译机制来执行不同于主机架构的代码。二是作为虚拟机监管器,模拟全系统,利用其他VMM(Xen, KVM, etc)来使用硬件提供的虚拟化支持,创建接近于主机性能的虚拟机。

用户可以通过不同Linux发行版所带有的软件包管理器来安装QEMU。如在Debian系列的发行版上可以使用下面的命令来安装:

sudo apt-get install qemu

或者在红帽系列的发行版上使用如下命令安装:

sudo yum install qemu -y

除此之外,也可以选择从源码安装。

##获取QEMU源码##

可以从QEMU官网上下载QEMU源码的tar包,以命令行下载2.0版本的QEMU为例:

$wget http://wiki.qemu-project.org/download/qemu-2.0.0.tar.bz2
$tar xjvf qemu-2.0.0.tar.bz2

如果需要参与到QEMU的开发中,最好使用Git获取源码:

$git clone git://git.qemu-project.org/qemu.git

##编译及安装##

获取源码后,可以根据需求来配置和编译QEMU。

$cd qemu-2.0.0 //如果使用的是git下载的源码,执行cd qemu
$./configure --enable-kvm --enable-debug --enable-vnc --enable-werror  --target-list="x86_64-softmmu"
$make -j8
$sudo make install

configure脚本用于生成Makefile,其选项可以用./configure --help查看。这里使用到的选项含义如下:

--enable-kvm:编译KVM模块,使QEMU可以利用KVM来访问硬件提供的虚拟化服务。
--enable-vnc:启用VNC。
--enalbe-werror:编译时,将所有的警告当作错误处理。
--target-list:选择目标机器的架构。默认是将所有的架构都编译,但为了更快的完成编译,指定需要的架构即可。

#二、基本原理#

QEMU作为系统模拟器时,会模拟出一台能够独立运行操作系统的虚拟机。如下图所示,每个虚拟机对应主机(Host)中的一个QEMU进程,而虚拟机的vCPU对应QEMU进程的一个线程。

QEMU结构图

系统虚拟化最主要是虚拟出CPU、内存及I/O设备。虚拟出的CPU称之为vCPU,QEMU为了提升效率,借用KVM、XEN等虚拟化技术,直接利用硬件对虚拟化的支持,在主机上安全地运行虚拟机代码(需要硬件支持)。虚拟机vCPU调用KVM的接口来执行任务的流程如下(代码源自QEMU开发者Stefan的技术博客):

open("/dev/kvm")
ioctl(KVM_CREATE_VM)
ioctl(KVM_CREATE_VCPU)
for (;;) {
     ioctl(KVM_RUN)
     switch (exit_reason) {
     case KVM_EXIT_IO:  /* ... */
     case KVM_EXIT_HLT: /* ... */
     }
}

QEMU发起ioctrl来调用KVM接口,KVM则利用硬件扩展直接将虚拟机代码运行于主机之上,一旦vCPU需要操作设备寄存器,vCPU将会停止并退回到QEMU,QEMU去模拟出操作结果。

虚拟机内存会被映射到QEMU的进程地址空间,在启动时分配。在虚拟机看来,QEMU所分配的主机上的虚拟地址空间为虚拟机的物理地址空间。

QEMU在主机用户态模拟虚拟机的硬件设备,vCPU对硬件的操作结果会在用户态进行模拟,如虚拟机需要将数据写入硬盘,实际结果是将数据写入到了主机中的一个镜像文件中。

#三、创建及使用虚拟机#

##命令行创建及启动虚拟机##

成功安装QEMU之后便可创建自己的虚拟机。具体步骤如下:

1, 使用qemu-img创建虚拟机镜像。虚拟机镜像用来模拟虚拟机的硬盘,在启动虚拟机之前需要创建镜像文件。

[kelvin@kelvin tmp]$ qemu-img create -f qcow2 fedora.img 10G
Formatting 'fedora.img', fmt=qcow2 size=10737418240 encryption=off cluster_size=65536 lazy_refcounts=off 
[kelvin@kelvin tmp]$ ls
fedora.img

-f选项用于指定镜像的格式,qcow2格式是QEMU最常用的镜像格式,采用写时复制技术来优化性能。fedora.img是镜像文件的名字,10G是镜像文件大小。镜像文件创建完成后,可使用qemu-system-x86来启动x86架构的虚拟机:

qemu-system-x86_64 fedora.img

此时会弹出一个窗口来作为虚拟机的显示器,显示内容如下:

QEMU虚拟机显示器输出

因为fedora.img中并未给虚拟机安装操作系统,所以会提示“No bootable device”,无可启动设备。

2, 准备操作系统镜像。

可以从不同Linux发行版的官方网站上获取安装镜像,以fedora20为例:

[kelvin@kelvin tmp]$ wget http://ftp6.sjtu.edu.cn/fedora/linux/releases/20/Live/x86_64/Fedora-Live-Desktop-x86_64-20-1.iso

3, 检查KVM是否可用。

QEMU使用KVM来提升虚拟机性能,如果不启用KVM会导致性能损失。要使用KVM,首先要检查硬件是否有虚拟化支持:

[kelvin@kelvin ~]$ grep -E 'vmx|svm' /proc/cpuinfo

如果有输出则表示硬件有虚拟化支持。其次要检查kvm模块是否已经加载:

[kelvin@kelvin ~]$ lsmod | grep kvm
kvm_intel             142999  0 
kvm                   444314  1 kvm_intel

如果kvm_intel/kvm_amd、kvm模块被显示出来,则kvm模块已经加载。最后要确保qemu在编译的时候使能了KVM,即在执行configure脚本的时候加入了–enable-kvm选项。

4, 启动虚拟机安装操作系统。

执行下面的命令启动带有cdrom的虚拟机:

[kelvin@kelvin tmp]$ qemu-system-x86_64 -m 2048 -enable-kvm fedora.img -cdrom ./Fedora-Live-Desktop-x86_64-20-1.iso

-m 指定虚拟机内存大小,默认单位是MB, -enable-kvm使用KVM进行加速,-cdrom添加fedora的安装镜像。可在弹出的窗口中操作虚拟机,安装操作系统,安装完成后重起虚拟机便会从硬盘(fedora.img)启动。之后再启动虚拟机只需要执行:

[kelvin@kelvin tmp]$ qemu-system-x86_64 -m 2048 -enable-kvm fedora.img

即可。

##图形界面创建及启动虚拟机##

命令行启动虚拟机比较繁琐,适合开发者,但对于普通用户来说,采用图形界面管理虚拟机则更为方便。采用图形界面管理QEMU虚拟机需要安装virt-manager,红帽系列的发行版只需要执行命令:

$sudo yum install virt-manager -y

安装完成后用root用户启动virt-manager:

$su -
#virt-manager

启动后的界面如下图所示:

virt-manager界面

点击左上角电脑图标即可创建虚拟机。按照步骤操作即可完成对虚拟机的创建。

Linux系统中的信号量机制

1、信号量的定义:

struct semaphore {
    spinlock_t lock;
    unsigned int count;
    struct list_head wait_list;
};

在linux中,信号量用上述结构体表示,我们可以通过该结构体定义一个信号量。

2、信号量的初始化:

可用void sema_init(struct semaphore *sem, int val);直接创建,其中val为信号量初值。也可以用两个宏来定义和初始化信号量的值为1或0:

DECLARE_MUTEX(name)	:	定义信号量name并初始化为1
DECLARE_MUTEX_LOCKED(name)	:	定义信号量name并初始化为0

还可以用下面的函数初始化:

void init_MUTEX(struct semaphore *sem);	 //初始化信号量的值为1
void init_MUTEX_LOCKED(struct semaphore *sem);	//初始化信号量的值为0

3、信号量的原子操作:

p操作:

  • void down(struct semaphore *sem); //用来获取信号量,如果信号量值大于或等于0,获取信号量,否则进入睡眠状态,睡眠状态不可唤醒
  • void down_interruptible(struct semephore *sem); //用来获取信号量,如果信号量大于或等于0,获取信号量,否则进入睡眠状态,等待信号量被释放后,激活该程。
  • void down_trylock(struct semaphore *sem); //试图获取信号量,如果信号量已被其他进程获取,则立刻返回非零值,调用者不会睡眠

v操作:

  • void up(struct semaphore *sem); //释放信号量,并唤醒等待该资源进程队列的第一个进程

4、经典同步问题的解决方案:

  • 生产者和消费者问题:

a、单缓冲区问题描述:生产者向消费者提供产品,它们共享一个有界缓冲区,生产者向其中投放产品,消费者从中取得产品。同时,每个进程都互斥的占用CPU。假定生产者和消费者是互相等效的,只要缓冲区未满,生产者就可以把产品送入缓冲区,类似的,只要缓冲区未空,消费者便可以从缓冲区中取走产品并消费它。生产者—消费者的同步关系将禁止生产者向已满的缓冲区中放入产品,也禁止消费者从空的缓冲区中获取产品

问题分析: 需要定义两个信号量,一个用于互斥访问缓冲区,另一个用于生产者与消费者之间的同步。s1=1; s2=0;

伪代码:

生产者进程            

while(1)                                         
{                                       
    printf(“I'm producing!\n”);                                        
    down_interruptible(&s1);                                         
    printf(“I'm putting a product into the buffer\n”);          
    up(&s1);                   
    up(&s2);                
}               

消费者进程

while(1)  
{
    down_interruptible(&s2);
    down_interruptible(&s1);
    printf(“I'm getting a product\n”);
    up(&s1);
    printf(“I'm consuming a product\n”);
}

b、多生产者、多消费者、n个缓冲区

问题描述:有一群生产者进程在生产产品,并将这些产品提供给消费者进程去消费。为使生产者进程与消费者进程并发执行,在两者之间设置了n个缓冲区,生产者将产品放入一个缓冲区中,消费者可以从一个缓冲区中取走产品去消费。要求生产者进程与消费者进程必须保持同步,即不允许生产者进程向一个满的缓冲区放产品,也不允许消费者从一个空的缓冲区取产品。

问题分析:该问题貌似比a问题复杂的多,首先我们定义一个数组buffer[n],来表示n个缓冲区,还需要定义两个变量:in 表示要存入的缓冲区的下标,out表示要取产品的缓冲区的下标。定义三个信号量:s1用于实现对缓冲池的互斥操作,empty表示空缓冲区的个数,full表示满缓冲区的个数。初值:in=out=0; s1=1; full=0; empty=n;

伪代码如下:

生产者进程                                                                  

while(1)                                                                          
{                                                                                      
    printf(“I'm producing!\n”);                                                   
    down_interruptible(&empty);                                           
    down_interruptible(&s1);                                                   
    printf(“I'm putting a product into the buffer[in]\n”);                 
                                                                                                          
    in = (in+1)%n;                                                                                
    up(&s1);                                                                                      
    up(&full);                                                                     
}                                                                                       

消费者进程

  • 哲学家进餐问题

    while(1) { down_interruptible(&full); down_interruptible(&s1); printf(“I’m getting a product from buffer[out]\n”); out = (out+1)%n; up(&empty); printf(“I’m consuming a product\n”); }

问题描述:五个哲学家共用一个圆桌,分别坐在周围的五张椅子上,在圆桌上有五只碗和五只筷子,他们交替进行思考和进餐。哲学家饥饿时便试图取最靠近他的两只筷子,当同时获得两只筷子时便可用餐,用餐完毕后放下筷子。

问题分析: 五只筷子为临界资源,定义包含五个元素的信号量数组来实现对筷子的互斥使用。chopstick[5],五个信号量的初值都为1。

伪代码:

第i(i=0,1,2,3,4)个哲学家进程:

while(1)
{
          down_interruptible(&chopstick[i]);
          down_interruptible(&chopstick[(i+1)%5]);
          printf(“I'm eating!\n”);
          up(&chopstick[i]);
          up(&chopstick[(i+1)%5);
}
  • 读者写者问题:

问题描述:一个文件可被多个进程共享,reader进程读取该文件,而writer进程负责写文件,允许多个reader进程同时读取文件,但不允许一个writer进程和其他reader进程或writer进程同时访问文件。

问题分析:进程对文件互斥访问的实现可借助一个信号量就可以搞定,但是我们需要引入一个count变量来记录reader进程的个数,对这个变量的访问也是互斥的,所以也需要引入一个信号量。定义信号量rs实现对count的互斥访问,定义ws实现对文件的互斥访问。两信号量初值都为1

伪代码:

reader进程                             	
{                                                                                          
        down_interruptible(&rs);                                                                                                                                                                                                   
        count++;
        up(&rs);  
        printf(“I'm reading!\n”);   
       down_interruptible(&rs);
       count--;
       if(count == 0){
               up(&ws);
        }
        up(&rs);
}       

 writer进程
 {
    down_interruptible(&ws);           
    if (count == 0){ 
        printf(“I'm writing!\n”);
        down_interruptible(&ws);                                     
    } 
    up(&ws);
}