分类目录Linux系统

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

 

在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愉快的玩耍.

阅读原文

使用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简易安装入门

基于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

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);
}

基于Linux与lpc3250开发板的交叉开发环境搭建

###一、Bootloader的安装(在windows下进行)

1、什么是Bootloader:

要想弄明白什么是Bootloader,我们先从PC上的bootloader说起。PC上的BIOS和硬盘上的引导记录有着和嵌入式开发板中的bootloader类似的作用。PC的Bootloader由BIOS和MBR组成,BIOS固化在主板的一个芯片上,MBR则是硬盘的主引导扇区的缩写。PC启动后,首先执行BIOS的启动程序,根据用户的COMS设置,BOIS加载硬盘MBR的启动数据,并把系统的控制权交给保存在MBR中的OS Loader(如grub),最后再由OS Loader将控制权交给OS内核。

了解了什么是PC中的Bootloader,我们再来看什么是嵌入式系统中的Bootloader。嵌入式系统中没有与BIOS类似的芯片,这就需要开发人员自己设计Boootloader。不过,我们不必从零开始写这些代码,已经有公司和组织为大多数嵌入式系统写好了Bootloader。

2、lpc3250的Bootloader组成:

  • kickstart:位于Flash的Block0,负责加载从Flash Block1开始的程序,这里只的是S1L。开发板上电后,kickstart被内部的IROM加载并执行。IROM只能加载Block1以内的映像,而kickstart被加载后将被允许加载从Flash Block1开始的多个Block的映像文件。所以,kickstart上电后,kickstart加载S1L,也可以直接加载放在Block1的应用程序。
  • S1L:对芯片和板子进行初始化,并提供一个用于应用程序开发和执行控制的监控程序。其常用功能如下: @擦写nandFlash @串口下载,SD卡下载 @设置CPU频率 @设置从SD卡,NandFlash启动 @加载引导Eboot
  • Eboot:设置内核在NandFlash中的位置,内核复制到RAM的位置,以及内核的大小;网络下载
  • Uboot:操作系统引导。Uboot的具体分析留到以后再说。

3、安装步骤:

  • 由于笔记本不带串口,所以第一步是找个usb串口连接线,并安装好驱动。
  • 将开发板的电源线连接好,然后连接开发板串口与PC上的usb串口。
  • 利用开发板所带光盘中的smartARM3250_boot.exe安装kickstart和S1L:进入到光盘中smartARM3250_boot.exe所在的目录,确保kickstart.bin和stage1.bin也在该目录下。运行smartARM3250_boot.exe。首先选择好串口,我的是com3。如果你不知道你的串口是多少的话,可以在右键单击我的电脑——》管理——》设备管理器。查看到自己的串口位置后,点击打开串口。点击装载Bootloader,软件会提示你短接jp6,点击确定,短接jp6后reset开发板,过几秒钟便提示装载成功。这儿的bootloader不是我们上面所讲的bootloader,它指的是bootloader.bin,我们

执行了上面过程后,smartARM3250_boot.exe就将bootloader.bin拷贝到开发板执行,其实我们可以把bootloader理解成smartARM3250_boot.exe的客户端软件。

装载bootloader.bin后,我们下来就正式安装kickstart了。在右下脚的Flash擦出中选择编程NandFlash, 块地址设为0,点击选择文件,选择kickstart.bin。点击编程。这样我们就把kickstart.bin装载到了NandFlash的Block0。用同样的方法我们可以装载S1L,只是要将块地址改为1。

  • 安装U-boot或Eboot:

a. 打开windows的超级终端,配置好参数后连接。reset开发板,进入到SmartArm3250的工作台,将光盘中的u-boot.bin或eboot.nb0拷贝到一张SD卡上,然后将SD卡插入到开发板的SD插槽中,在超级终端中输入命令:load blk u-boot.bin(eboot.nb0) raw 0x83fc0000,将u-boot.bin或eboot.nb0加载到SDRAM中。 b. 在超级终端中敲入命令:nsave。将u-boot.bin(eboot.nb0)写入到NANDFlash。 c. 最后输入命令:aboot flash raw 0x83fc0000,设置从NANDFlash启动U-boot(Eboot)。

###二、Linux系统(Ubuntu)下所需要的软件的安装步骤:

1、交叉工具链的安装:

a、什么是交叉工具链:在PC机上开发嵌入式软件所需要的编译器、make等工具的集合。

b、安装步骤:

  • 将光盘中的tc-nxp-lnx-armvfp-4.3.2-1.i386.rpm拷贝到PC机的桌面。

  • 打开终端,输入sudo rpm –force-debian -ivh tc-nxp-lnx-armvfp-4.3.2.1-1.i386.rpm命令。

  • 执行上述命令后,就将交叉工具链安装到/opt/nxp目录中了,现在我们将安装路径加入到PATH变量中去。在终端中输入命令:

    gedit ~/.bashrc

在打开的.bashrc文件的末尾添加如下语句:

export PATH="$PATH:/$HOME/bin:/opt/nxp/gcc-4.3.2-glibc-2.7/bin"

保存关闭,注销当前用户,重新登录

  • 检测是否安装好交叉工具链:在终端中输入arm-vfp-linux-gnu-并按TAB键,如果能看到很多arm-vfp-linux-gnu-为前缀的命令,则说明交叉开发工具链已经安装好了。

2、NFS服务器的安装:

(NFS的详细介绍请参考NFS)

a、NFS的功能:

NFS是网络文件系统的缩写,它的功能是把NFS服务器(即Linux主机)的某个目录挂载到开发板的文件系统上(开发板上Linux系统的安装我们在后面会讲到),这样,开发板就可以执行该目录中的可执行程序。这样做的优点在于,不用将程序写入开发板的Flash,减少了对Flash的损害。

b、NFS的安装:

在Ubuntu下的安装很easy: sudo apt-get install nfs-sever

3、TFTP服务器的安装:

a、什么是tptp:TFTP是远程文件传输协议的缩写,其作用是将主机中设定目录下的文件拷贝到开发板的文件系统中,它与NFS的区别显而意见。

b、安装:还是很easy:sudo apt-get install tftp-hpa tftpd-hpa xinetd。第一个是客户端程序,第二个是服务器端程序,第三个是守护进程。

c、Ubuntu系统在安装完成后自动启动tftp服务,也可以通过命令:

sudo service xinetd start或restart命令启动。

d、然后进入xinetd.d文件夹(cd /etc/xinetd.d),查看是否有一个tftp文件,如果没有就新建一个,如果有的话就查看内容是否与下面的一致,不一致则修改,内容如下:

service tftp  
{  
    socket_type = dgram  
    wait = yes  
    disable = no  
    user = root  
    protocol = udp  
    server = /usr/sbin/in.tftpd  
    server_args = -s /home/tftpboot  
    log_on_success += PID HOST DURATION  
    log_on_failure += HOST  
}  

e、然后保存关闭。输入命令:sudo /etc/init.d/xinetd restart重启服务。

TFTP的使用我们到后面在介绍。

4、minicom的安装和配置:

a、minicom的功能:就四个字,超级终端

b、安装:sudo apt-get install minicom

c、配置:在终端输入:sudo minicom -s选择串口设置,串口设备设为/dev/ttyUSB0(也许你的不一样,可以在dev目录下查看),波特率设为115200,硬件流和软件流控制都设为No。回车退回到刚进入时的界面,选择save setup as dfl。

###三、Linux内核,安全文件系统和根文件系统的安装:

1、连接好串口线和网线。

2、插入光盘,将光盘中的uImage文件拷贝到/var/lib/tftpboot目录下。

3、在主机终端中输入:sudo chmod -R 777 /var/lib/tftpboot 命令来改变该目录权限。(这个目录是tftp服务器默认存放要传输文件的目录)

4、打开另一个终端,输入命令:sudo minicom

5、reset开发板,这时终端就进入了U-boot的工作台。

5、在工作台中输入命令:tftp 80008000 uImage将内核镜像文件拷贝到开发板内存中。

6、在工作台中输入命令:nand write.jffs2 0x80008000 0x00200000 $(filesize)将镜像文件从内存中拷贝到NANDFlash相应的位置。

7、在工作台中输入命令:setenv kernelsize $(filesize) 设置内核大小为镜像文件大小。

8、在工作台中输入命令:saveenv 保存设置

9、安装安全文件系统:

  • 将光盘中的safefs.cramfs文件放到/var/lib/tftpboot目录下。

  • 在工作台中输入命令:

    tftp 80008000 safefs.cramfs nand erase clean 0x00600000 $(filesize)
    nand write.jffs2 0x80008000 0x00600000 $(filesize)

10、将光盘中的rootfs.tar.bz2文件和burn文件拷贝到一张SD卡上。

11、将SD卡插入开发板插槽中,在工作台输入命令:run safemode。

12、坐等烧写完成,reset开发板。

ls命令实现分析

###一、ls命令的功能分析

使用man ls命令查看ls命令手册(功能描述和主要选项摘录如下):

List information about the FILEs (the current directory by default).Sort entries alphabetically if none of -cftuvSUX nor –sort.

列出文件(默认当前目录)信息,如果没有-cftuvSUX和–sort选项,就按照字母顺序排序。

-a, --all   do not ignore entries starting with .:不忽略以.开始的隐藏文件

-A, --almost-all   do not list implied . and ..:不列出 .(当前目录)和..(上级目录)

-B, --ignore-backups  do not list implied entries ending with ~:忽略以~结尾的备份文件

-c with -lt: sort by, and show, ctime (time of last modification of file status information) with -l: show ctime and  sort  by  name otherwise: sort by ctime:和-lt一起使用,则显示ctime(最后修改文件信息的时间),并按ctime排序显示;和-l一起使用,则显示
ctime,但只按文件名的字母顺序排序;其他,按ctime排序显示。/*该选项和-t选项在单独使用的时候是等价的,但在和-l选项配合使用的时候,-c的功能会被屏蔽,而-t选项不会*/

-d, --directory   list directory entries instead of contents, and do not  dereference symbolic links:不是列出该目录的文件信息,而是列出该目录项。不追踪符号链接的实际位置。

-F, --classify   append indicator (one of */=>@|) to entries:在每个entry后面加上标识文件内容的符号:

         * : 标识可执行文件     / : 标识目录          = : 套接字文件
         @ : 符号链接文件       | : 管道文件

-l     use a long listing format:以长格式显示。

###二、ls所用到的系统调用:

使用strace ls命令我们可以查看ls命令使用到的系统调用,其中最重要的几个为:

open(".", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_DIRECTORY|O_CLOEXEC) = 3
getdents64(3, /* 68 entries */, 32768)  = 2240
getdents64(3, /* 0 entries */, 32768)   = 0
close(3)                                = 0

1、open系统调用:

打开当前目录文件,返回获得的文件描述符。

  • O_RDONLY:只读 O_NONBLOCK:以非阻塞的方式打开文件 O_LARGEFILE:允许打开大文件
  • O_DIRECTORY:如果路径不是目录,则打开错误 O_CLOEXEC:在创建新的进程后关闭文件描述符

2、close系统调用:

关闭文件描述符。

3、getdents64:

读取当前目录下的文件。

三、getdents64的系统调用服务例程:

由于getdents64实现了ls核心功能,下面着重分析getdents64系统调用在内核态下的实现。getdents64在fs/readdir.c中定义如下:

 275SYSCALL_DEFINE3(getdents64, unsigned int, fd,
 276                struct linux_dirent64 __user *, dirent, unsigned int, count)
 277{
 278        struct file * file;
 279        struct linux_dirent64 __user * lastdirent;
 280        struct getdents_callback64 buf;
 281        int error;
 282
 283        error = -EFAULT;
 284        if (!access_ok(VERIFY_WRITE, dirent, count))
 285                goto out;
 286
 287        error = -EBADF;
 288        file = fget(fd);
 289        if (!file)
 290                goto out;
 291
 292        buf.current_dir = dirent;
 293        buf.previous = NULL;
 294        buf.count = count;
 295        buf.error = 0;
 296
 297        error = vfs_readdir(file, filldir64, &buf);
 298        if (error >= 0)
 299                error = buf.error;
 300        lastdirent = buf.previous;
 301        if (lastdirent) {
 302                typeof(lastdirent->d_off) d_off = file->f_pos;
 303                if (__put_user(d_off, &lastdirent->d_off))
 304                        error = -EFAULT;
 305                else
 306                        error = count - buf.count;
 307        }
 308        fput(file);
 309out:
 310        return error;
 311}

getdents64首先调用fget函数得到目录文件的file结构体,再调用虚拟文件系统提供的vfs_readdir函数,读取目录项,该函数的定义也在fs/readdir64中:

int vfs_readdir(struct file *file, filldir_t filler, void *buf)
  24{
  25        struct inode *inode = file->f_path.dentry->d_inode;
  26        int res = -ENOTDIR;
  27        if (!file->f_op || !file->f_op->readdir)
  28                goto out;
  29
  30        res = security_file_permission(file, MAY_READ);
  31        if (res)
  32                goto out;
  33
  34        res = mutex_lock_killable(&inode->i_mutex);
  35        if (res)
  36                goto out;
  37
  38        res = -ENOENT;
  39        if (!IS_DEADDIR(inode)) {
  40                res = file->f_op->readdir(file, buf, filler);
  41                file_accessed(file);
  42        }
  43        mutex_unlock(&inode->i_mutex);
  44out:
  45        return res;
  46}

该函数首先通过file结构体得到inode,然后从inode中获得并执行file_operations结构体中的读取目录函数(底层文件系统提供)file->f_op->readdir(file, buf, filler)。

综上所述,实际上对文件进行操作的是底层文件系统提供的函数,它通过file_operations结构体可被上层的虚拟文件系统调用,而用户程序又可通过系统调用进入内核态,调用虚拟文件系统提供的接口函数。

FUSE分析总结

###FUSE简介及原理

FUSE(Filesystem in Userspace)是sourceforge上的一个开源项目,它可以为用户提供编写用户态文件系统的接口。使用FUSE,用户可以不必熟悉Kernel代码,使用标准C库、FUSE库以及GNU C库便可设计出自己需要的文件系统。

FUSE由三个部分组成:FUSE内核模块、FUSE库以及一些挂载工具。

FUSE内核模块实现了和VFS的对接,它看起来像一个普通的文件系统模块;另外,FUSE内核模块实现了一个可以被用户空间进程打开的设备,当VFS发来文件操作请求之后,它将该请求转化为特定格式,并通过设备传递给用户空间进程,用户空间进程在处理完请求后,将结果返回给FUSE内核模块,内核模块再将其还原为Linux kernel需要的格式,并返回给VFS。如下图所示:

FUSE内核模块

图1.0 FUSE内核模块

FUSE库负责和内核空间的通信,它接收来自/dev/fuse的请求,并将其转化为一系列的函数调用,并将结果写回到/dev/fuse。

挂载工具用以实现对用户态文件系统的挂载。

FUSE的作用可通过下面两幅图说明:

Ext4文件系统的文件操作流程

图1.1 Ext4文件系统的文件操作流程

上图为Ext4文件系统的文件操作流程,当系统用户在输入ls /home/kelvin命令之后,最终会调用到Ext4文件系统内的相关函数来对文件进行处理,并将结果返回。

FUSE文件操作流程

图1.2 用户态文件系统的文件操作流程

上图是基于FUSE所写的一个用户态文件系统tfs的文件操作流程,系统用户在该文件系统(/tmp/fuse为tfs的挂载点)内所执行的ls –l /tmp/fuse命令通过FUSE最终会调用到tfs里所写的钩子函数。这样,通过FUSE,可以在用户态设计并实现自己的文件系统,而不必了解kernel的代码编写规范。

###FUSE代码编写规范

FUSE给用户提供了fuse_operations结构体,用户可实现具体的钩子函数,然后将这些钩子函数挂载到该结构体。main()函数只需调用fuse_main()就可以了,其他的工作交给FUSE去做。

FUSE代码编写框架

图1.3 用户态文件系统代码编写框架

###fuse_main()的处理流程

fuse_main()函数处理流程

图1.4 fuse_main()函数的处理流程

fuse_main()被调用后,它调用fuse_mount(),创建新的进程fusermount,来检查FUSE内核模块是否加载,并返回文件描述符给fuse_main()。fuse_new()为文件系统分配数据空间。fuse_loop()从/dev/fuse 读取文件系统调用,调用fuse_operations结构中的处理函数,返回调用结果给/dev/fuse。

使用FUSE的注意事项

  • FUSE的作用在于使用户能够绕开内核代码来编写文件系统,可文件系统如果要实现对具体的设备的操作的话必须要使用设备驱动提供的接口,而设备驱动位于内核空间,FUSE便无法将文件系统挂载到具体设备上去。所以,基于FUSE所写的文件系统通常是将文件当做虚拟的磁盘,并使用C所提供的文件操作接口;或者是映射一个目录到文件系统。

  • FUSE给各钩子函数传递的path参数的/指的是文件系统的/目录。

NBD-网络块设备[翻译]

1997年4月,Pavel Machek 写了他的网络块设备代码,并被当时的Linux Kernel 2.1.55接受。Pavel 在随后的四个发行版(对应的内核版本为55、101、111、132)中维护并升级了他的代码。Andrzej M. Krzysztofowicz贡献了64位机上运行的版本,随后Stephen Tweedie 为其提出了许多的专业建议,尤其是引入了基于信号量的锁机制,使得代码在对称多处理器系统中能够安全的运行。作者们已将其增强以便于运用于工业环境之中。本文描述了网络块设备、驱动、以及它的开发历史。

NBD的驱动程序提供了一种在这个面向网络的世界中更为普遍的访问模型。它在本地客户端模拟了一个块设备,比如一个磁盘或者是一块磁盘分区,但实际提供物理支持的却是通过网络连接的远程服务器。在本地,该设备看起来是一个磁盘分区,但那只是远程服务器的表面。远程服务器是一段轻量级的守护进程代码,它提供对远程设备的访问,而且该守护进程并不需要运行在linux平台之上。本地操作系统必须是linux,而且要支持Linux内核NBD驱动,并且能够运行本地客户端进程。安装NBD可被用来进行远程存储和备份,而且能用来在地球的任意一个角落虚拟地传送物理设备。

NBD在本地客户端代表远程资源

图1.NBD在本地客户端代表远程资源

NBD有一些UNIX系统组件的经典特征:简单、小巧、多功能。文件系统能被挂载到NBD上(图1),而且NBD设备也能够用来组成冗余磁盘阵列(RAID)。在本地Linux上挂载一个EXT2文件系统到NBD,其传输率要比在同样的远程机器上挂载一个NFS快(表1是接近Pavel最初写的驱动代码的计时)。

在此输入图片描述

图2.使用NBD的冗余磁盘阵列

write cmd.	dd=if=/dev/zero of=/dev/nd0 bs=1024 count=32000	
NBD bs=1K	0.04user 5.54system 0:26:67elapsed 20.9%CPU	1.23MBps
write cmd.	dd=if=/dev/zero of=/dev/nfs bs=1024 count=32000	
NFS r/wsize=1K	0.15user 7.46system 0:43.58elapsed 17.0%CPU	0.75MBps
write cmd.	dd=if=/dev/zero of=/dev/nfs bs=4096 count=8000	
NFS r/wsize=4K	0.03user 3.94system 0:35.18elapsed 11.0%CPU	0.93MBps

表 1. NBD与NFS在一个端口上的写传输速率比较

挂载到NBD上的Linux EXT2文件系统,缓冲区大小接近1.5KB,其性能赶得上默认条件下的NFS。也就是说,它的大小是以太网默认传输单元(MTU/MRU)的大小——1.5KB。这恰好是NFS默认缓冲区大小(1KB)的1.5倍。NBD因为使用TCP而不是UDP作为传输协议而具有很好的恢复力。TCP包含它固有的一致性以及恢复机制,因而比UDP协议庞大。但对NBD来说,TCP的开销会被它节省的重新传输和校正码所需带宽抵消。

NBD能够被用作一个中型邮件服务器A的实时镜像。故障转移可以通过在另一个房间里放置一台用100BT网络连接的备份服务器B来实现。NBD设备将主服务器和备份服务器连接,并提供了主服务器RAID-1镜像的一半(Y),另一般是主服务器自己的用于存放邮件的分区(X)。组合成的设备XY将作为mail spool被挂载。

当服务器A失效时,运行在B上的一个守护进程能够探测到NBD连接的断开,然后检查镜像Y的mail spool, 校验最小的不完整性,并将其在本地作为mail spool挂载,最后在B上启动A广播的邮件交换IP地址的别名。当A修复之后,会被守护进程检测到,别名将会被丢弃。NBD连接随后会被重新修复,A上的分区X将会重新和NBD镜像Y保持同步。主服务器RAID-1设备会被重新调度,NBD镜像也会重新从属于它。邮件服务将会以默认的配置恢复。使主从服务器设置对称是可能的,但不适宜在这里描述细节。

与其他备选方案相比,这种方法具有许多优点。其一,比如说,维护一个空的邮件服务器,在主服务器宕机的时候将其唤醒。这会使得许多已经放到spool中的邮件在停机期间不可被使用。但重新整合是很容易的,将相关的文件连接到恢复后的服务器也很简单。但主服务器上的某些邮件可能会丢失。

另一种解决办法是将邮件服务器分散到一些不同的机器上,这些机器通过NFS挂载同样的spool区域。然而,NFS的文件锁过去从未被证明是完全可靠的,NFS也从未令人满意的支持由于多个用户同时选择下载50MB的邮件目录引起的事件的激增。传输率也会随着文件尺寸成指数地变慢。在不利的网络条件下,软模式的传输率极有可能崩溃。如果NFS在服务启动的时候为了追求高的可靠性而以同步(硬)模式挂载,一旦NFS服务失效,将会导致所有客户端停机。NFS服务不得不独立地建立镜像。

内核源码编译

编译内核的目的在于把纷繁复杂的内核源码编译成一个可执行的镜像文件。当然,内核功能复杂,并不是所有的 功能都需要用到,所以,我们在使用make对其编译前,要做一件事——配置。kconfig和make就好比将军和士兵。kconfig是指导方向的,make是干活的。kconfig对内核进行配置,告诉make,哪些功能需要编译进内核,哪些功能要作为模块编译,make便根据配置文件来进行编译。

###编译步骤

  • 获取内核源码(这几天kernel.org被黑了,该步骤方法暂时用不了):

1、使用git工具:

ubuntu下安装git:

sudo apt-get install git

使用git检出Linus分支的版本:

git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.t.git

检出之后便可以通过下列命令进行升级:

git pull

使用上述两个命令便可以获得内核源码的最新版本。

2、直接从kernel.org上下载源码包:

源码包有两种格式:

linux-x.y.z.tar.bz2和linux-x.y.z.tar.gz

bz2结尾的压缩包使用的是bzip2压缩格式,而gz结尾的压缩包使用的是gzip压缩格式。你可以任选一种,只是解压所用的命令不同罢了。一般来说,使用的是bz2格式,因为它具有较好的压缩效率。解压命令对应如下:

bz2:

tar xjvf linux-x.y.z.tar.bz2

gz:

tar xzvf linux-x.y.z.tar.gz

  • 配置内核

配置内核的工具有下面几种:

  • make menuconfig : 终端图形界面,依赖于ncurses库
  • make config : 纯文本界面
  • make xconfig : X-window界面
  • make gconfig : 和xconfig类似,不过依赖于GTK库
  • make allyesconfig :编译内核尽可能多的功能
  • make allmodconfig :尽可能多地编译成内核模块配置的结果是产生.config文件
  • make oldconfig :如果你有.config文件,你就可以使用。和make config类似,但只询问新特性的配置
  • make defconfg :按照默认的配置文件arch/i386/defconfig进行配置。可再使用make menuconfig进行配置

我一般使用的是make menuconfig,因为它是比较折中的方案。使用make menuconfig前要安装ncurses库:

sudo apt-get install libncurses5-dev

安装完成后在源码目录下执行命令:

make menuconfig

根据需要,进行适当的配置后,便可进行编译操作了。

  • 编译

生成.config命令后在源码目录下执行:

make bzimage -j8

完成后会在arch/i386/boot/下生成bzimage压缩镜像。

  • 安装

make module_install:将编译生成的内核模块复制到/lib/modules/<kernel-version>目录中去。

make install:复制内核镜像到/boot目录中并重命名为vmlinuz-x.y.z;生成initrd-<kernel-version>.img;配置引导文件。

Linux基础知识总结

###一:什么是Linux?

Linux被称为类Unix操作系统,遵循POSIX标准。Linux与Unix的最大不同在于源代码的开放性和自由性。

Linux的发音:Linux发音

Linux的发展历史:1991年9月17日,Linus Torvalds 在网上发布了Linux的0.01版本。

  • 1994年3月13日,在众多程序员的努力下,Linux 1.0版本正式发布。呵呵,人多力量大,要学会分享知识。
  • 1996年,Linux 2.0发布,其重要特点是开始支持对称多处理器。
  • 2004年12月18日,Linux2.6.0版本发布。

###二:Linux所遵循的标准:

1、POSIX(Portable Oprating System Interface of Unix):即可移植操作系统接口,由IEEE于1988年发布。其作用是使在某一Unix版本下开发的应用程序也能应用于其他Unix版本,以此保证了软件代码的可移植性。

2、LSB(Linux Standard Base):由于Linux遵循POSIX标准,不同Linux发行版之间的源代码级有很好的兼容性,但二进制文件的兼容性不足,为解决这一问题,Linux Standard Base 项目应运而生,其主要目的是开发一系列标准,保证Linux不同发行版间的兼容性。

3、FHS(Filesystem Hierarchy Standard):即文件系统分级结构标准,是LSB标准的一部分,详细规定了类Unix操作系统中各种应用软件、管理工具、脚本和帮助文件所处的位置。

###三:GNU和GPL

GNU(GNU is not Unix):项目名称,该项目旨在实现一个自由并完整的类Unix操作系统,是自由软件发展的重要组成部分。

GPL(General Public License):该授权模式下的软件需满足以下条件:

  • 自由软件指的是软件开发者有发布软件的自由
  • 用户可获得源程序
  • 用户能修改软件或将它的一部分用于新的自由软件
  • 禁止任何人不承认软件开发者的权利。如果用户修改了自由软件,这些规定转化为该用户的责任。

###四:Linux目录结构:

  • /bin:存放对系统运行极为重要的二进制文件,也包括/usr/bin目录中文件的符号链接,同时还有一些用户命令。
  • /etc:存放系统配置文件。(只能存放静态文件)
  • /home:存放与各个用户相关的文件。
  • /tmp:临时目录,很多程序要在该目录下创建临时文件。
  • /var:存放经常变化的信息,如邮件、日志等。
  • /proc:伪目录,系统将当前运行的进程映射为文件,通过修改该目录的文件值可以完成对内核运行参数的修改。
  • /boot:存放启动所需文件。
  • /dev:存放特殊文件和设备文件。
  • /usr:存放与用户相关的程序和库文件。
  • /sbin:存放与系统相关的可执行程序。

###五:Linux系统组成:

用户进程、系统调用接口、Linux内核、硬件

###六:内核结构:

  • 进程调度:控制进程对cpu的访问,也就是根据一定的进程调度算法选择合适的进程运行。
  • 内存管理:用于控制多个进程安全地访问内存。Linux支持虚拟内存管理。
  • 虚拟文件系统:隐藏了各个不同文件系统实现的具体细节,为外界使用文件提供统一的接口。
  • 网络接口:提供了对各种标准协议的存取和各种网络硬件的支持。
  • 进程间通信:支持进程间各种通信机制。

###七:进程状态:

  • 可运行态:运行态和就绪态。
  • 浅度睡眠:可被信号和时钟中断唤醒的状态。
  • 深度睡眠:不可被信号和时钟中断唤醒的状态。
  • 暂停状态:进程暂停,接受类似于调试程序的处理。
  • 僵死状态:进程执行结束,大部分系统资源被释放,但进程pcb并未被释放。

FUSE

###一、FUSE简介

FUSE(用户空间文件系统)是这样一个框架,它使得FUSE用户在用户态下编写文件系统成为可能,而不必和内核打交道。FUSE由三个部分组成,linux内核模块、FUSE库以及mount 工具。用户关心的只是FUSE库和mount工具,内核模块仅仅提供kernel的接入口,给了文件系统一个框架,而文件系统本身的主要实现代码位于用户空间中。 FUSE库给用户提供了编程的接口,而mount工具则用于挂在用户编写的文件系统。

FUSE起初是为了研究AVFS(A Virtual Filesystem)而设计的,而现在已经成为SourceForge的一个独立项目,目前适用的平台有Linux, FreeBSD, NetBSD, OpenSolaris和Mac OS X。官方的linux kernel版本到2.6.14才添加了FUSE模块,因此2.4的内核模块下,用户如果要在FUSE中创建一个文件系统,需要先安装一个FUSE内核模块,然后使用FUSE库和API来创建。

###二、FUSE特性

  • 库文件和 API简单,极大地方便了用户的使用
  • 安装简便,不需要加补丁或者重新编译 kernel
  • 执行安全,使用稳定
  • 高效,相对于其它用户态文件系统实例
  • 非特权用户可以使用
  • 基于 linux2.4.x 和 2.6.x 内核,现在可以支持JavaTM 绑定,不必限定使用C和C++来编 写文件系统

###三、源代码目录

  • ./doc 包含FUSE相关文档
  • ./include 包含了FUSE API头,对创建文件系统有用,主要用fuse.h
  • ./lib 存放FUSE库的源代码
  • ./util 包含了FUSE工具库的源代码
  • ./example 参考的例子

###四、安装

FUSE的源码安装类似于其他软件,只需要在FUSE的源码目录下执行如下命令即可:

./configure
make
make install(以root身份执行)

###五、FUSE operations

FUSE使用fuse_operations来给用户提供编程结构,让用户通过注册自己编写的函数到该结构体来实现自己的文件系统。

struct fuse_operations {
    int (*getattr) (const char *, struct stat *);
    int (*readlink) (const char *, char *, size_t);
    int (*mknod) (const char *, mode_t, dev_t);
    int (*mkdir) (const char *, mode_t);
    int (*unlink) (const char *);
    int (*rmdir) (const char *);
    int (*symlink) (const char *, const char *);
    int (*rename) (const char *, const char *);
    int (*link) (const char *, const char *);
    int (*chmod) (const char *, mode_t);
    int (*chown) (const char *, uid_t, gid_t);
    int (*truncate) (const char *, off_t);
    int (*utime) (const char *, struct utimbuf *);
    int (*open) (const char *, struct fuse_file_info *);
    int (*read) (const char *, char *, size_t, off_t, struct fuse_file_info *);
    int (*write) (const char *, const char *, size_t, off_t, struct fuse_file_info *);
    int (*statfs) (const char *, struct statvfs *);
    int (*flush) (const char *, struct fuse_file_info *);
    int (*release) (const char *, struct fuse_file_info *);
    int (*fsync) (const char *, int, struct fuse_file_info *);
    int (*setxattr) (const char *, const char *, const char *, size_t, int);
    int (*getxattr) (const char *, const char *, char *, size_t);
    int (*listxattr) (const char *, char *, size_t);
    int (*removexattr) (const char *, const char *);
    int (*opendir) (const char *, struct fuse_file_info *);
    int (*readdir) (const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *);
    int (*releasedir) (const char *, struct fuse_file_info *);
    int (*fsyncdir) (const char *, int, struct fuse_file_info *);
    void *(*init) (struct fuse_conn_info *conn);
    void (*destroy) (void *);
    int (*access) (const char *, int);
    int (*create) (const char *, mode_t, struct fuse_file_info *);
    int (*ftruncate) (const char *, off_t, struct fuse_file_info *);
    int (*fgetattr) (const char *, struct stat *, struct fuse_file_info *);
    int (*lock) (const char *, struct fuse_file_info *, int cmd, struct flock *);
    int (*utimens) (const char *, const struct timespec tv[2]);
    int (*bmap) (const char *, size_t blocksize, uint64_t *idx);
};

###六、hello示例文件系统源码分析

FUSE在源码目录example下有一些示例文件系统,通过阅读这些示例文件系统可以掌握FUSE用户态文件系统的编写规范。下面以hello.c为例分析FUSE的编写规范:

#define FUSE_USE_VERSION 26
#include <fuse.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>

static const char *hello_str = "Hello World!\n";
static const char *hello_path = "/hello";

/该函数与stat()类似,用于得到文件的属性,将其存入到结构体struct stat中/

static int hello_getattr(const char *path, struct stat *stbuf)
{
    int res = 0;
    memset(stbuf, 0, sizeof(struct stat));	 //用于初始化结构体stat
    if (strcmp(path, "/") == 0) {
    stbuf->st_mode = S_IFDIR | 0755;	 //S_IFDIR 用于说明/为目录,详见S_IFDIR定义
    stbuf->st_nlink = 2;	 //文件链接数
} else if (strcmp(path, hello_path) == 0) {
    stbuf->st_mode = S_IFREG | 0444;	//S_IFREG用于说明/hello为常规文件
    stbuf->st_nlink = 1;
    stbuf->st_size = strlen(hello_str);	 //设置文件长度为hello_str的长度
} else
    res = -ENOENT;	 //返回错误信息,没有该文件或目录
    return res;	 //执行成功返回0
}

/该函数用于读取/目录中的内容,并在/目录下增加了. .. hello三个目录项/

static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,  off_t offset, struct fuse_file_info *fi)    
{    
    (void) offset;        
    (void) fi;        
    if (strcmp(path, "/") != 0)     
        return -ENOENT;
/*
fill的定义:
typedef int (*fuse_fill_dir_t) (void *buf, const char *name,const struct stat *stbuf, off_t off);
其作用是在readdir函数中增加一个目录项
*/
    filler(buf, ".", NULL, 0);
    //在/目录下增加. 这个目录项
    filler(buf, "..", NULL, 0);
    // 增加.. 目录项
    filler(buf, hello_path + 1, NULL, 0);
    //增加hello目录项
    return 0;
}

/用于打开hello文件/

static int hello_open(const char *path, struct fuse_file_info *fi)
{
    if (strcmp(path, hello_path) != 0) 
        return -ENOENT;
    if ((fi->flags & 3) != O_RDONLY)
        return -EACCES;
    return 0;
}

/读取hello文件时的操作,它实际上读取的是字符串hello_str的内容/

static int hello_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
{
    size_t len;
    (void) fi;
    if(strcmp(path, hello_path) != 0)
        return -ENOENT;
    len = strlen(hello_str);
    if (offset < len) {
        if (offset + size > len)
            size = len - offset;
        memcpy(buf, hello_str + offset, size);
    } else
    size = 0;
    return size;
}

/注册上面定义的函数/

static struct fuse_operations hello_oper = {
    .getattr = hello_getattr,
    .readdir = hello_readdir,
    .open = hello_open,
    .read = hello_read,
};

/用户只需要调用fuse_main(),剩下的事就交给FUSE了/

int main(int argc, char *argv[])
{
    return fuse_main(argc, argv, &hello_oper, NULL);
}

终端运行:

~/fuse/example$ mkdir /tmp/fuse	 //在/tmp下建立fuse目录,用于挂载hello文件系统
~/fuse/example$ ./hello /tmp/fuse	 //挂载hello文件系统
~/fuse/example$ ls -l /tmp/fuse	//执行ls时,会调用到readdir函数,该函数会添加一个hello	 文件
总用量 0

-r--r--r-- 1 root root 13 1970-01-01 07:00 hello

~/fuse/example$ cat /tmp/fuse/hello	//执行cat hello时,会调用open以及read函数,将
Hello World!	 字符串hello_str中的内容读出
~/fuse/example$ fusermount -u /tmp/fuse //卸载hello文件系统

通过上述的分析可以知道,使用FUSE必须要自己实现对文件或目录的操作,系统调用也会最终调用到用户自己实现的函数。用户实现的函数需要在结构体fuse_operations中注册。而在main()函数中,用户只需要调用fuse_main()函数就可以了,剩下的复杂工作可以交给FUSE。

Linux用户管理

###一、用户管理:

Linux系统用户分为三类:超级用户、普通用户和伪用户。其具体区别如下:

  • 超级用户:具有管理系统的一切权限。UID为0。
  • 普通用户:具有有限的权限。UID从500到6000。
  • 伪用户:在/etc/passwd中有记录,但因记录中shell为空,所以不能登录系统。其作用是为了方便管理系统,满足相应的系统进程对文件属主的需要。

用户帐号的配置文件是/etc/passwd,可用sudo gedit /etc/passwd命令查看和修改。文件内容格式如下:

        root:x:0:0:root:/root:/bin/bash
        daemon:x:1:1:daemon:/usr/sbin:/bin/sh
        bin:x:2:2:bin:/bin:/bin/sh
        sys:x:3:3:sys:/dev:/bin/sh
        sync:x:4:65534:sync:/bin:/bin/sync
        games:x:5:60:games:/usr/games:/bin/sh
        man:x:6:12:man:/var/cache/man:/bin/sh
        lp:x:7:7:lp:/var/spool/lpd:/bin/sh
        mail:x:8:8:mail:/var/mail:/bin/sh
        news:x:9:9:news:/var/spool/news:/bin/sh
        uucp:x:10:10:uucp:/var/spool/uucp:/bin/sh
    ……

其含义为:

用户名:用户密码:用户id:组id:用户相关信息:用户家目录:用户shell

用户密码的配置文件为/etc/shadow,同样用sudo gedit /etc/shadow命令查看。

    root:!:14866:0:99999:7:::
    daemon:*:14728:0:99999:7:::
    bin:*:14728:0:99999:7:::
    sys:*:14728:0:99999:7:::
    sync:*:14728:0:99999:7:::
    ……
    hplip:*:14728:0:99999:7:::
    gdm:*:14728:0:99999:7:::
    kelvin:$6$CILudWuh$Dv6cGVZlLBKoCXUubKYeo3W4s9GmBegBJPgiIRzlRZ7O1uYO6mEzLtv
    rfFEozeSjHB56R7.Qt/rRgADFjyPda1:14866:0:99999:7:::
    sshd:*:14867:0:99999:7:::

任何用户都可查看passwd文件,但只有root才能查看shadow文件。shadow文件内容的含义为:

用户名:加密口令(!表示不能登录):密码最后修改时间:密码最大时间间隔:最小时间间隔:警告时间:不活动时间:失效时间

创建一个帐号有如下方法:

  1. 在passwd中增加一条记录;创建用户家目录;设置用户家目录的配置文件;设置用户口令;
  2. useradd 命令或adduser命令;用userdel或deluser命令删除用户,注意加-r选项才能删除用户家目录和所有用户信息。

###二、用户组管理:

用户组分为:

  • 私有组:当创建一个新的用户,没有指定该用户所属组时,系统则建立一个和该用户同名的私有组。
  • 标准组:除开私有组以外的所有组。

用户组的配置文件为/etc/group,用sudo gedit /etc/group命令查看该文件,其内容形式如下:

    root:x:0:
    daemon:x:1:
    bin:x:2:
    sys:x:3:
    adm:x:4:kelvin
    tty:x:5:
    disk:x:6:
    lp:x:7:
    mail:x:8:
    news:x:9:
    uucp:x:10:
    man:x:12:
    proxy:x:13:
    ……

其含义为:

组名:口令:组id:成员

添加或删除组的命令:groupadd、addgroup、delgroup、groupdel。具体用法和adduser类似。

添加用户到组de命令:gpasswd -a 用户名 组名。

将用户从组中删除:gpasswd -d 用户名 组名。

groups 用户名:查看用户的组状态。即用户属于哪些组。

finger 用户名:查看用户信息。

Linux权限管理

Linux权限管理是其一大特色,优秀的权限管理机制为Linux安全性提供了可靠的保障。

###一、用户权限管理:

root用户是系统的超级用户,是Linux系统的CEO,它具有最高的管理权限,所以一般不用该用户登录系统进行日常的操作与维护,root可将某些权限赋予其他用户来管理系统的某些资源。

su命令用来切换用户,用该命令切换用户时,用户身份和环境变量都发生改变。例如:

kelvin@kelvin-laptop:~$ ls
examples.desktop  workplace  公共的  模板  视频  图片  文档  下载  音乐  桌面
kelvin@kelvin-laptop:~$ su ws
密码: 
ws@kelvin-laptop:/home/kelvin$ 

在ubuntu系统中,有些动作需要管理员的权限才能执行,可用sudo来提升权限。若在/etc/目录下建立nologin文件,则只允许root用户登录。相反,删除该文件则所有用户都可以登录系统。

###二、文件权限管理:

使用ls -l命令可以以长格式显示该目录下文件和子目录信息。如:

kelvin@kelvin-laptop:~$ ls -l
总用量 40
-rw-r--r-- 1 kelvin kelvin  179 2010-09-14 09:39 examples.desktop
lrwxrwxrwx 1 kelvin kelvin   37 2010-09-15 21:49 linux -> /home/kelvin/workplace/linux-2.6.35.4
drwxr-xr-x 3 kelvin kelvin 4096 2010-09-15 17:13 workplace
drwxr-xr-x 2 kelvin kelvin 4096 2010-09-14 09:49 公共的
drwxr-xr-x 2 kelvin kelvin 4096 2010-09-14 09:49 模板
drwxr-xr-x 2 kelvin kelvin 4096 2010-09-14 09:49 视频
drwxr-xr-x 2 kelvin kelvin 4096 2010-09-14 09:49 图片
drwxr-xr-x 2 kelvin kelvin 4096 2010-09-14 15:09 文档
drwxr-xr-x 2 kelvin kelvin 4096 2010-09-14 14:57 下载
drwxr-xr-x 2 kelvin kelvin 4096 2010-09-14 09:49 音乐
drwxr-xr-x 2 kelvin kelvin 4096 2010-09-15 17:18 桌面

第一部分给出了文件或目录的相关权限信息:

drwxr-xr-x:总共有十个位。

第1位给出了该项的属性,即是文件还是目录,或者是链接文件等。

-:表示是文件,如上面的examples.desktop就是一个文件;
d:表示是目录,如上面的workplace;
l:表示是链接文件;

第2、3、4位给出了文件或目录所有者的权限,第5、6、7位给出了文件或目录所属用户组的权限,第8、9、10位给出了其他用户权限。

r:为读权限。
w:为写权限。
x:为执行权限。目录的执行权限的意思是可以用cd命令进入该目录。

chmod命令:该命令用来修改文件或目录的访问权限。一般有两种使用方式:

第一种方式为:chmod a+r 文件或目录名

其中a可用u、g、o替换,+可用=、-替换,r可用w、x替换。

a:表示修改所有用户的权限。包括u、g、r。
u:表示只给文件或目录所有者修改权限。
g:表示给文件所有组修改权限。
o:表示给其他用户修改权限。
+:表示增加某种权限。
-:表示减去某种权限。
=:表示赋予某种权限。

第二种方式为:

用数字表示权限:

r:用4表示 w:用2表示 x:用1表示。

则1:–x 2:-w- 3:-wx 4:r– 5:r-x 6:rw- 7:rwx

于是可用“chmod 777 文件或目录名”命令来修改权限。三个7中第一个代表所有者权限,第二个代表所有组权限,第三个代表其他用户权限。当然,也可用类似于“chmod u+2 文件目录名”的方式来修改权限。

-R选项表示包括子目录的权限也改变。

chown命令:改变文件或目录所有者。-R选项同样表示包含子目录。

格式:chown 用户 文件或目录

chgrp:改变所属组,用法类似于chown。

chown group.user 文件或目录名,可同时改变所有者和所属组。

umask:用于设定文件或目录刚创建是的权限。目录为755+umask值=777,文件644+umask值=777。

粘着位t:对于权限值为777的目录可设置粘着位t,即:drwxrwxrwt。其含义为,任何用户的可在该目录中创建和修改自己的文件,也可以查看别人的文件,但不能删除或修改其他用户的文件。

s:对于可执行文件,若将该文件用户或组权限的x用s替换,则相应用户便具有了该执行文件拥有者或拥有组的身份。设置s位的作用可用如下的例子阐明:

保存用户密码的文件存放于/etc/shadow中,其权限为:

-rw-r----- 1 root shadow 1067 2010-09-15 21:33 /etc/shadow

即:只有该文件的所有者root用户才能具有读写该文件的权利,那么为什么普通用户可以通过passwd命令修改自己的密码呢?我们看一下passwd命令这个可执行文件的权限:

-rwsr-xr-x 1 root root 37140 2010-01-27 01:09 /usr/bin/passwd

该命令有s位,所以当普通用户执行该命令时,他便有了passwd所有者root的身份,自然可以修改属于 root用户的文件shadow。

Make

###一、make的功能:

make是一个用来维护程序模块关系和生产可执行文件的工具,他可以根据程序修改的情况重新编译链接生成的中间代码或最终的可执行文件。执行make命令需要一个Makefile文件,来定义整个项目的编译规则。makefile定义了模块间的依赖关系,指定文件的编译顺序,以及编译所使用的命令。有了make和Makefile文件,整个项目的源程序可以自动编译,极大的提高了软件开发效率。

###二、Make的一般使用:

1、Makefile的基本构成:

Makefile由规则构成,一条规则生成一个或多个目标文件,其格式如下:

目标文件列表 分隔符 依赖文件列表 [;命令]  //[]中的内容可选
    [命令]
    [命令]

例如:

main:main.o module1.o module2.o
	gcc main.o module1.o module2.o -o main

第二行是命令必须以Tab键开头,第一行是说明模块间的依赖关系,所以不加Tab,若加Tab,make就认为这是一条命令。以#开头的行为注释行,makefile中若用到#,可用#;同样,$应该用$$。在依赖列表后加上分号后,可直接跟上命令。

命令之间可以插入多个空行,但每行必须有Tab键,如果一行过长,可以在行末输入一个\,用反斜杠连接的行都被看成一行来处理,反斜杠和新行之间不能有空格。Makefile也可以命名为makefile,若命名为其他文件名,则需要用-f或–file选项来告知make哪一个是makefile文件。

2、Makefile文件的构成:

一个完整的makefile文件由5个部分构成:显式规则、隐含规则、变量、文件指示和注释。

显式规则:一条显式规则指名了目标文件、目标文件的依赖关系、命令。有些规则没有命令,只是说明文件之间的依赖关系。

隐含规则:由make根据目标文件而自动推导出的规则。例如:module2.o:head1.h,实际上等价于module2.o:module2.c head1.h; gcc -c module2.c -o module2.o

变量:类似于c语言中的宏。

文件指示:包括三个部分,一个类似于c语言中的include语句,可以将另一个makefile文件包含进来;二是根据情况指定makefile中的有效部分,就像c语言中的预编译#if一样;三是定义一个多行的命令。

3、makefile的基本语法:

  • |的作用:

    foo:foo.c | somelib
      gcc -o foo foo.c somelib
    

    当somelib文件的时间戳比foo晚时,不用重新编译foo。

  • 命令行属性:

可在命令前、Tab键后加上如下符号:

  -:执行本命令行如果遇到错误,继续执行而不退出make。
  +:总是执行该命令,即使执行make时使用了-n,-q,-t选项。
  @:执行命令时不在屏幕上输出该命令的内容。
  • 伪目标:

先看以下代码:

clean:
	-rm -rf *.o

因为clean后没有依赖文件,所以clean被认为是最新,不会执行rm命令,若要执行rm,需要执行make clean命令。但如果当前目录下已经有一个名叫clean的文件,则make clean也不能使得rm命令执行。这时需要引入伪目标才能搞定。

.PHONY:clean
clean:
	-rm -rf *.o

.PHONY的依赖文件为伪目标,作用是伪目标的命令即使在当前目录下存在与伪目标同名的文件时也执行该命令。

  • 特殊目标:

.PHONY:伪目标,如上

.IGNORE:对于该目标后的依赖文件,生成时如遇到错误则可跳过错误继续执行,不会中断make。

.SUFFIXES:该目标的依赖被认为是一个后缀列表,在检查后缀规则时使用。

.SILENT:生成该目标文件的依赖文件所执行的命令都不被打印,如果其后无依赖文件,则所有的命令都不会被打印。

.PRECIOUS:该目标的依赖文件将不会被删除。

.INTERMEDIATE:该目标的依赖文件被当作是中间文件对待。

  • 多个目标:

一个规则中可以有多个目标,这些目标有相同的依赖文件

  • 搜索目录:

通常在一个大的项目中,会把头文件、源文件、库文件放在不同的目录下。当目录发生改变后,只需改变依赖文件的搜索目录。 make定义了一个叫VPATH的变量,在当前目录中搜索不到依赖文件时,便到VPATH定义的目录中去寻找。也可用关键字vpath 定义搜索位置:

vpath <pattern> <directories>
vpath :清除所有已被设置好的文件搜索目录。

如:vpath %.h ../headers表示在../headers目录下搜索所有.h结尾的头文件。

  • 变量:

makefile中通常可定义变量,make在执行时会把变量名出现的地方用变量值代替。

引用变量:$()或者${}

定义变量:

 = :定义的变量在执行命令时才展开;
 :=:定义的变量立即展开;
 ?=:在此之前没有给该变量赋值才会给该变量赋值
 +=:追加变量值,与原变量值之间用空格隔开

预定义变量:

makefile 中预定义了许多变量,在隐含规则中通常会用到这些变量:

     宏名		初始值		说明
     CC			cc			默认使用的编译器
     CFLAGS		-o			编译器使用的选项
     MAKE		make		make命令
     MAKEFLAGS	空 			make命令的选项
     SHELL					默认使用的shell名
     PWD					运行make时的当前路径
     AR			ar			库管理命令
     ARFLAGS   	-ruv		库管理选项
     LIBSUFFIXE	.a			库的后缀
     A			a			库的扩展名

自动变量:

它们的值在make运行过程中动态的改变,是隐含规则所必需的变量。

     $@:表示一个规则中的目标文件名。
     $%:当规则中的目标文件是一个静态库文件时,$%就代表静态库的一个成员名。如果目标不是静态库文件,则该变量
         值为空。
     $<:规则中的第一个依赖文件名。
     $>:当规则是一个静态库文件时,该变量表示静态库名。
     $?:所有比目标文件新的依赖文件列表,以空格分隔。如果目标是静态库文件,该变量代表的是
         库成员。
     $^:规则的所有依赖文件列表,以空格分割。如果目标是静态库文件,代表的是库成员。
     $+:和$^类似,不同的是该变量不除去重复的文件。
     $*:去掉后缀的目标文件名。
  • 条件语句:

    ifeq ($(CC),gcc)
    libs=$(libs_for_gcc)
    else
    libs=$(normal_libs)
    endif
    foo:foo.c
       $(CC) -o foo foo.c $(libs)
    

此外,还有ifneq,ifdef,ifndef

  • 使用库:

建立和维护库时,把库名作为目标文件,需要加入库中的文件作为依赖文件。如:

  mylib.a:mylib.a(file1.o) mylib.a(file2.o)或
  mylib.a:mylib.a(file1.o file2.o)

接着输入命令:

ar -ruv 库名 目标文件名

三、make命令的常用选项:

-C dir或--directory=DIR:在读取Makefile文件之前,先切换到dir目录下,即把dir目录作为当前目录。
-d:输出所有的调试信息。
-e或--environment-overrides:不允许makefile对系统环境变量进行重新赋值。
-f filename或者--file=FILE或者--makefile=FILE:使用指定文件作为makefile文件。
-i或者--ignore-errors:忽略执行make时产生的错误,不退出make。
-h或者--help:打印出帮助信息。
-n或者--just-print:只打印出要执行的命令,但不执行命令。
-o filename或者--old-file=FILE:不重建filename及依赖于filename的文件。
-p:执行命令之前,打印出make读取的makefile的所有数据,同时打印出make的版本信息。如果只打印信息而
	不执行命令,可使用make -qp ,查看make执行前的隐含规则和预定义变量,使用make -p-f /dev/null。
-q:不执行任何命令,返回0表示没有重建目标,返回1表示存在重建目标,返回2表示有错误发生。
-r:忽略隐含规则。
-R:取消预定义变量。同时打开-r选项。
-s:执行但不显示所执行的命令。
-t:把所有目标文件的最后修改时间设置为当前系统时间。
-v:打印make版本信息。

perf ,比较好的一个程序性能测试工具

面对一个问题程序,最好采用自顶向下的策略。先整体看看该程序运行时各种统计事件的大概,再针对某些方向深入细节。而不要一下子扎进琐碎细节,会一叶障目的。

对于优化自己写的代码,cpu bound 型 和 IO bound 型是不一样的:

  • cpu bound 型:所谓cpu bound型指的是程序大部分时间都在使用CPU。
  • IO bound 型:由cpu bound型的定义就不难推出了。

perf stat 命令用于统计进程总体的信息

    /*******************************************************************************/
    $ perf stat ./Joseph_ring
     Performance counter stats for './Joseph_ring':
    
             19.755435 task-clock                #    0.000 CPUs utilized          
                   429 context-switches          #    0.022 M/sec                  
                     5 CPU-migrations            #    0.000 M/sec                  
                   137 page-faults               #    0.007 M/sec                  
            27,255,530 cycles                    #    1.380 GHz                    
         <not counted> stalled-cycles-frontend 
         <not counted> stalled-cycles-backend  
             6,521,404 instructions              #    0.24  insns per cycle        
             1,157,604 branches                  #   58.597 M/sec                  
               161,429 branch-misses             #   13.95% of all branches        
    
         439.901478124 seconds time elapsed
    /*******************************************************************************/

task-clock:CPU 利用率,该值高,说明程序的多数时间花费在 CPU 计算上而非 IO。

Context-switches:进程切换次数,记录了程序运行过程中发生了多少次进程切换,频繁的进程切换是应该避免的。

Cache-misses:程序运行过程中总体的 cache 利用情况,如果该值过高,说明程序的 cache 利用不好

CPU-migrations:表示进程 t1 运行过程中发生了多少次 CPU 迁移,即被调度器从一个 CPU 转移到另外一个 CPU 上运行。

Cycles:处理器时钟,一条机器指令可能需要多个 cycles

Instructions: 机器指令数目。

IPC:是 Instructions/Cycles 的比值,该值越大越好,说明程序充分利用了处理器的特性。

branches:待查

branch misses:待查

perf top 命令可查看系统的实时信息

例如系统中最耗时的内核函数或某个用户进程:


    /*******************************************************************************/
    $perf top
    
                 samples  pcnt function                       DSO
    
                 _______ _____ ______________________________ __________________
    
    
    
                  133.00  9.4% system_call                    [kernel.kallsyms] 
    
                  126.00  8.9% __ticket_spin_lock             [kernel.kallsyms] 
    
                   49.00  3.5% schedule                       [kernel.kallsyms] 
    
                   49.00  3.5% __ticket_spin_unlock           [kernel.kallsyms] 
    
                   43.00  3.0% pthread_mutex_lock             libpthread-2.13.so
    
                   40.00  2.8% _nv027676rm                    [nvidia]          
    
                   40.00  2.8% __pthread_mutex_unlock_usercnt libpthread-2.13.so
    
                   38.00  2.7% __memset_sse2                  libc-2.13.so      
    
                   32.00  2.3% unix_poll                      [kernel.kallsyms] 
    
                   25.00  1.8% __sincos                       libm-2.13.so      
    
                   24.00  1.7% update_curr                    [kernel.kallsyms] 
    
                   24.00  1.7% sched_clock_local              [kernel.kallsyms] 
    
    /**********************************************************************************/
    
### **perf record** 

记录单个函数级别的统计信息,并使用 perf report 来显示统计结果,以此可以找到热点:

    /**********************************************************************************/
    # Events: 46  cpu-clock
    
    #
    
    # Overhead      Command      Shared Object                  Symbol
    
    # ........  ...........  .................  ......................
    
    #
    
        63.04%  Joseph_ring  [kernel.kallsyms]  [k] 0xc10e7ef2
    
         6.52%  Joseph_ring  libc-2.13.so       [.] __GI_vfprintf
    
         4.35%  Joseph_ring  libc-2.13.so       [.] __printf
    
         4.35%  Joseph_ring  libc-2.13.so       [.] _IO_new_file_xsputn
    
         2.17%  Joseph_ring  libc-2.13.so       [.] _itoa_word
    
    /**********************************************************************************/

perf record -e cpu-clock -g 给出函数的调用关系,以便于找到次级热点:

    /**********************************************************************************/
    
    # Events: 47  cpu-clock
    
    #
    
    # Overhead      Command      Shared Object                  Symbol
    
    # ........  ...........  .................  ......................
    
    #
    
        72.34%  Joseph_ring  [kernel.kallsyms]  [k] 0xc11017a7
    
                |
    
                --- 0xc150a125
    
                   |          
    
                   |--33.33%-- 0xc105e3a7
    
                   |          0xc105e4f6
    
    
    /**********************************************************************************/

Kobject浅析

面向对象的思想的确在应用软件的开发中颇具优势,它让一个个纯逻辑的函数和数据变成了一个个有生命的个体。鉴于性能的考虑,系统软件的实现(例如linux kernel)并没有采用面向对象的语言(如C++、Java)。但这丝毫没有影响到用小c找对象。

简单来说,一个对象包含数据以及对这些数据的操作。如果把银行比作一个对象的话,银行里的RMB就是数据、而银行的工作人员就相当于对象中方法(即操作数据的结构)。如果,我们想打劫银行的话,我们只需要拿着枪指着工作人员说,“亲,给我拿500万出来”。当然,如果你是一个比较牛逼的劫匪你可能要1000万,或者更多。这没有关系,因为无论你要多少钱,你都不必亲自动手去取,工作人员会帮你办好这一切。当你拎着一袋子钱溜走的时候,你可能会想说,“自从有了面向对象,腰不酸了、腿不疼了,打劫也更有效率了”。

这个例子只是想说明面向对象的一个显而易见的优点——给劫匪,哦不,给用户提供了可直接使用的接口。当然,面向对象的优点可不只这一点。

kobject是Linux设备驱动模型的核心部分,它的作用是简单点说就是嵌入到设备和驱动相关的结构体之中。既可以将这些设备和驱动组织成树形结构,又可以让用户通过sysfs直接控制驱动和设备。让我们来搭讪一下kobject,从她那儿看看有对象的好处。

我们哲学老师在提问一个学生之前都会先问学生的家乡在哪儿。据他的逻辑应该是一方水土养一方人,从一个人的家乡可以大致推断出他的性格等。 不管这个理论对不对,我们先看看kobject的家乡——include/linux/kobject.h,kobject在这里定义:

struct kobject {
    const char *name;
    struct list_head entry;
    struct kobject *parent;
    struct kset *kset;
    struct kobj_type *ktype;
    struct sysfs_dirent *sd;
    struct kref kref;
    unsigned int state_initialized:1;
    unsigned int state_in_sysfs:1;
    unsigned int state_add_uevent_sent:1;
    unsigned int state_remove_uevent_sent:1;
    unsigned int uevent_suppress:1;
};

每一个kobject实例都有一个名字name,用于标识这个实例。还有一个母亲parent,用于建立kobject的家族树。还有一个比较熟悉的面孔struct list_head entry,不用说,它的作用是把我们的kobject组织成双向链表。sd这个成员的作用和sysfs相关,kobject是组成sysfs树形结构的结点。kref是该结构体的引用计数,实质是一个原子型的整形量。下面几个unsigned int型的表示kobject的状态,kobject过得好还是不好,是不是党员,都在这里记录着。每一个kobject的结构体都有一个ktype,用来决定kobject被创建或销毁时应该如何处理:

struct kobj_type {
    void (*release)(struct kobject *kobj);
    const struct sysfs_ops *sysfs_ops;
    struct attribute **default_attrs;
    const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
    const void *(*namespace)(struct kobject *kobj);
};

release是个回调函数,当kobject的引用计数为0时会被调用。当然,这个函数是需要我们自己完成的。其作用通常是释放kobject守护的结构体。每一类kobject都有自己的默认的属性,它放在结构体数组default_attrs中。每一个属性都应该在sysfs中对应一个文件。而sysfs_ops则存放着读写这些文件时的处理。

每一个kobject都可以属于某一个kset,一个kset就是一群kobject的集合。当然,物以类聚,人以群分,这些kobject要走到一起,当然是要有些共同特点的:

struct kset {
    struct list_head list;
    spinlock_t list_lock;
    struct kobject kobj;
    const struct kset_uevent_ops *uevent_ops;
};

kset是kobject的容器,也就是说,它把kobject又封装了一下。没一个kset结构体里面存放一个kobject,kset通过list连接起来,从而属于这一集合的所有kobj也连接起来了。uevent_ops这个结构体存放了对这一组object的操作。具体什么操作,咱下回分解。

kobject就好比每个人的守护天使,她通常不单独存在,而是作为其他结构体的成员出现,类似于struct list_head。在一个结构体中,找到嵌入于其中的kobject是很easy的事,就像领导找下属,随叫随到的那种。但是kobject要找到嵌入它的结构体的指针却没那么容易了。幸好,kernel给我们提供了一个可以做这件事的宏:

contarner_of(pointer, type, member)

这个宏具体怎么用咱就不说了。google上百度一下,或者看下源码很容易就明白的。

有了这个宏,kobject守护的对象(设备和驱动)和kobject之间的联系就完全的建立起来了,kobject它妈叫它回家吃饭的时候就可以先找到设备和驱动,kobject要去打酱油的时候也可以叫上设备和驱动。

有了这个联系,系统里所有的设备和驱动就可以通过kobject给管理起来了。kobject和sysfs勾搭在一起就给用户层提供了修改设备和驱动参数的一种方式。

以下代码展示了如何利用kobject使用sysfs。

/**
 * This is a simple module for kobject test.And it is licensed under the terms
 * of GNU/GPL v3 or later.
 * Wang Sen (C) <wangsen@linux.vnet.ibm.com>
 * 2012.3.23
 */
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/list.h>
#include <linux/stat.h>
#include <linux/string.h>

#define BUF_SIZE 128

MODULE_LICENSE("GPL");

static char asset[BUF_SIZE] = "Hello Kitty";

static void test_release(struct kobject *kobj);
static ssize_t test_show(struct kobject *, struct attribute *, char *);
static ssize_t test_store(struct kobject *, struct attribute *, const char *, size_t);

static void test_release(struct kobject *kobj)
{
	if (kobj == NULL) {
		return;
	}
	
	printk("------[kobject_test]Hello,test_release is dancing\n");
}

static ssize_t test_show(struct kobject *kobj, struct attribute *attr, char *buf) 
{
	sprintf(buf, "asset:%s\nkobject_name:%s\n", asset,kobj->name);
	return strlen(buf);
}

static ssize_t test_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t size) 
{
	if (strlen(buf) > BUF_SIZE) {
		return E2BIG;
	}
	memset(asset, 0, BUF_SIZE);
	sprintf(asset, "%s", buf);
	return strlen(buf);
}

struct attribute test_attr = {
	.name = "info",
	.mode = S_IRWXUGO,
}; 

struct attribute test_attr_1 = {
	.name = "addr",
	.mode = S_IRWXUGO,
}; 

struct attribute *attr_array[] = {
	&test_attr,
	&test_attr_1,
	NULL
};

struct sysfs_ops test_sysfs_ops = {
	.show = test_show,
	.store = test_store,
};

struct kobject test_kobject;
struct kobj_type test_kobj_type= {
	.release = test_release,
	.sysfs_ops = &test_sysfs_ops,
	.default_attrs = attr_array,
};

static int __init kobj_test_init(void)
{
	printk("------kobject init...");
	kobject_init_and_add(&test_kobject, &test_kobj_type, NULL, "test_object_of_kelvin");
	return 0;
}

static void __exit kobj_test_exit(void)
{
	kobject_put(&test_kobject);
	printk("------See you");
}

module_init(kobj_test_init);
module_exit(kobj_test_exit);

MODULE_AUTHOR("Wang Sen <senwang@linux.vnet.ibm.com>");

参考文献: 《LINUX设备驱动程序》 第三版 Jonathan Corbet,Alessandro Rubini,Greg Kroah-hartman http://linux.chinaunix.net/techdoc/system/2008/07/19/1018480.shtml http://blog.21ic.com/user1/5593/archives/2010/69112.html http://blog.csdn.net/yili_xie/article/details/5835935

使用mock来编译和管理RPM软件包

buildroot

在打包时用到的spec文件中包含一些tag,这些对大小写不敏感的tag用冒号来定义。BuildRoot就是其中的一个tag。例如,在libvirt的spec文件中BuildRoot的定义如下:

BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root

BuildRoot设置的目录会被当作临时的根目录,%install部分安装的文件都会被临时安装到这里。之所以引入BuildRoot是为了在打包过程中不影响现在的系统。 RPM宏设定的BuildRoot默认值是~/rpmbuild/BUILDROOT, 可以在spec文件中设置该tag,或者在rpmbuild命令执行时使用- -buildroot选项来指定。

mock的功能

mock不只是将文件安装到Buildroot指定的根目录,而是创建一个打包的沙盒(sandbox),挂载一些必要的文件系统(proc,sys等),将打包过程所用到的软件包(BuildRequires指定)都安装到沙盒中,然后将指定的SRPM包进行编译,生成最终的RPM包。mock不仅可以使打包过程变得整洁,更能够测试BuildRequires是否完整。除了打包之外,mock也可以用来制作沙盒来测试软件包。

安装mock

使用YUM安装fedora维护者工具fedora-packager后,mock和koji作为依赖也被安装到系统中了。执行以下命令即可:

sudo yum install fedora-packager

简单的配置mock

在使用mock之前需要将当前用户加入到mock用户组,并使用户登陆到该用户组:

sudo usermod -a -G mock [User name] && newgrp mock

使用mock来打包

使用mock打包需要配置文件来指定安装软件包所用到的YUM仓库,/etc/mock目录下有许多这样的配置文件。配置文件可以通过-r选项来指定,如果没有指定,则使用默认的配置文件/etc/mock/default.cfg。

mock libvirt-1.2.2-1.fc20.src.rpm

将会在BUILDROOT目录下挂载一些必要的文件必要的文件系统,并安装打包过程需要用到的软件包,最终生成RPM包。

RPM软件包管理机制之旅

    Linux下的man命令十分实用,可以查看Linux命令的手册。但这些手册只适用于忘记命令的选项时查询之用,如果用来学习Linux下类似于Git, RPM这样庞大的工具就有点吃力了,可谓事倍功半。我在学习Git的时候读过一篇文档——gittutorial,使用:

$man gittutorial

命令可以调出该文档。这篇文档并不涵盖git的方方面面,只是介绍了Git管理项目的常规用法,非常适合初学者快速入门。所以本文试图仿照该文档的写法,并不会详细地介绍RPM包的方方面面,而是介绍RPM包最常用的一些功能,这些功能将会在我们对红帽系列发行版系统的日常管理中非常重要。

软件包管理机制与RPM

   开源软件可以用Tarball的方式来从源代码安装软件(1),这样的安装方式对普通用户来说很麻烦。比如要升级软件,还需要先更新源代码,再重新编译、安装。Linux采用了RPM和DPKG等软件包管理机制来管理软件,直接给用户提供二进制软件包,并且将整个系统的软件包信息建立成数据库,以便于软件的升级、验证和卸载。RedHat、Fedora、SUSE、CentOS等发行版采用RPM包管理机制,而Debian、Ubuntu等发行版采用DPKG方式来管理软件包。

   所谓RPM软件包或者平时叫的RPM包指的是包含软件运行所需的二进制文件、文档、函数库等内容的RPM格式的文件,以rpm作为文件的后缀名。如:

qemu-img-1.4.2-3.fc19.i686.rpm

qemu-img是包的名字;1.4.2是软件版本号;3是release号,指的是同一版本第3次构建的软件包(或称为打包);fc19指的是Linux发行版为Fedora 19;i686是软件运行的平台架构,可以是i386、i686、x86_64、ppc64、s390x、noarch(与平台无关的软件包)等,RPM要求打包的环境要与安装软件包的环境(平台架构、发行版、发行版的版本等)一致或相当。

RPM软件包的增删改查及验证

1. 安装RPM软件包到系统

   现在的系统很少用到直接使用RPM包来安装软件,而是采用YUM(后面介绍)来安装。使用RPM包来安装软件首先需要获得适合当前系统的RPM包,各个发行版通常会提供这些包的下载,如可以在i686平台、fedora19上安装的软件包可以在这里找到:

http://mirrors.ustc.edu.cn/fedora/linux/releases/19/Fedora/i386/os/Packages/

   安装软件包可以使用rpm命令,加上-i选项即可。例如,安装qemu-img软件包可以采用如下命令:

#rpm -ivh qemu-img-1.4.2-3.fc19.i686.rpm

-i选项表示rpm命令将安装qemu-img软件包;-v选项可以显示更详细的安装信息;-h显示安装进度。采用rpm -i命令来安装软件包并不会去解决软件包之间的依赖问题。如果说软件包B依赖于软件包A,那么在安装B之前A必须已经在系统中安装。rpm -i如果遇到依赖关系的问题,只会提示需要依赖哪些包,而不会去自动地将这些包都安装上。qemu-img依赖于glib库,如果我们将系统上的glib包删掉,再执行rpm -i来安装qemu-img就会产生如下的提示信息:

[root[@localhost](https://my.oschina.net/u/570656) 下载]# rpm -e glib2-2.36.3-3.fc19.i686 --nodeps 
[root[@localhost](https://my.oschina.net/u/570656) 下载]# rpm -q glib2-2.36.3-3.fc19.i686
未安装软件包 glib2-2.36.3-3.fc19.i686 
[root[@localhost](https://my.oschina.net/u/570656) 下载]# rpm -ivh qemu-img-1.4.2-3.fc19.i686.rpm 
错误:依赖检测失败:
libglib-2.0.so.0 被 qemu-img-2:1.4.2-3.fc19.i686 需要
libgthread-2.0.so.0 被 qemu-img-2:1.4.2-3.fc19.i686 需要

   如果要安装qemu-img岂不是要手动地将所有依赖的包安装上去才行?而且,qemu-img所依赖的包可能又会依赖另外的包。如此一来,安装RPM包岂不是非常复杂?聪明的Linux hacker们当然不会让Linux的世界乱套。YUM就是为了解决软件包之间的依赖性而生,我们稍后会对YUM做进一步介绍,现在先抛开包的依赖性不说,看看如何在红帽系列发行版上来删除RPM软件包。

2. 删除RPM软件包

   删除RPM软件包的命令和安装命令同样简单,只需要在rpm命令之上使用-e选项即可,同样值得一提的是删除软件包也需要注意软件包之间的依赖性,比如说,如果软件包A依赖于软件包B,那么应该先卸载软件包A,再卸载B,否则会导致软件A不可用。再用qemu-img的软件包为例,qemu-img会被libvirt-deamon所依赖,所以在删除qemu-img的时候会提示错误:

[root[@localhost](https://my.oschina.net/u/570656) 下载]# rpm -e qemu-img 
错误:依赖检测失败:
/usr/bin/qemu-img 被 (已安裝) libvirt-daemon-1.0.5.6-3.fc19.i686 需要

3. 升级RPM软件包

   如果系统上已经安装了glib2-2.36.3-2.fc19.i686这个包,现在发现在fedora的官网上提供了更新包glib2-2.36.3-3.fc19.i686,那该如何升级呢?使用rpm -Uvh或者rpm -Fvh即可。-U和-F的区别在于,-U允许系统未安装将要升级的包,在升级的时候执行安装动作,而-F则不能升级未安装的包。我们使用下面的命令可以将glib2升级:

[root[@localhost](https://my.oschina.net/u/570656) 下载]# rpm -q glib2-2.36.3-2.fc19.i686
glib2-2.36.3-2.fc19.i686
[root@localhost 下载]# rpm -Uvh http://mirrors.ustc.edu.cn/fedora/linux/updates/19/i386/glib2-2.36.3-3.fc19.i686.rpm
[root@localhost 下载]# rpm --rebuilddb 
[root@localhost 下载]# rpm -q glib2-2.36.3-2.fc19.i686
未安装软件包 glib2-2.36.3-2.fc19.i686 
[root@localhost 下载]# rpm -q glib2-2.36.3-3.fc19.i686
glib2-2.36.3-3.fc19.i686

4. 查看RPM软件包信息

   有时候,我们需要查询软件包的信息,rpm -q选项提供了查询RPM包信息的一些功能。

例如:

查询软件包qemu-img-1.4.2-3.fc19.i686.rpm是否安装到系统:

[kelvin@localhost 下载]$ rpm -qp qemu-img-1.4.2-3.fc19.i686.rpm 
qemu-img-1.4.2-3.fc19.i686

-qp后面指定具体的包名,用于查询某具体的RPM包的信息,而不是已经安装到系统的RPM包的信息,如果想要查询已经安装到系统的包的信息,可以去掉-p选项。

在安装一个RPM包之前,我们通常想知道它安装了哪些文件:

[kelvin@localhost 下载]$ rpm -qpl qemu-img-1.4.2-3.fc19.i686.rpm 
/usr/bin/qemu-img
/usr/bin/qemu-io
/usr/bin/qemu-nbd
/usr/share/man/man1/qemu-img.1.gz
/usr/share/man/man8/qemu-nbd.8.gz

-q选项也可以被用来查询软件包的依赖关系:

[kelvin@localhost 下载]$ rpm -qpR qemu-img-1.4.2-3.fc19.i686.rpm 
libaio.so.1
libaio.so.1(LIBAIO_0.1)
libaio.so.1(LIBAIO_0.4)
libc.so.6
......
rtld(GNU_HASH)
rpmlib(PayloadIsXz) <= 5.2-1

如果想知道一个文件是属于哪个文件包的,可以通过下面的命令来实现:

[kelvin@localhost 下载]$ rpm -qf /usr/bin/qemu-img
qemu-img-1.4.2-12.fc19.i686

5. 验证软件包中的文件是否被修改过

   RPM机制提供了一种非常实用的功能,可以让我们查看到系统中哪些软件包的文件被修改过,从而可以看出是否有病毒或者是恶意软件。我们可以使用rpm -Va来查看系统中所有被更该过的文件,如果有二进制文件被改动过,我们应该特别留意这些文件。下面的例子演示了被修改过的/usr/bin/qemu-img文件通过强制重新安装来还原的过程:

[root@localhost 下载]# rpm -qf /usr/bin/qemu-img 
qemu-img-1.4.2-12.fc19.i686
[root@localhost 下载]# echo "abc" >> /usr/bin/qemu-img 
[root@localhost 下载]# rpm -Va | grep .*qemu.*
S.5....T.    /usr/bin/qemu-img
[root@localhost 下载]# rpm -ivh qemu-img-1.4.2-3.fc19.i686.rpm --force
准备中...                          ################################# [100%]
正在升级/安装...
   1:qemu-img-2:1.4.2-3.fc19          ################################# [100%]
[root@localhost 下载]# rpm -Va | grep .*qemu.*
.........    /usr/bin/qemu-img (已被替换)
.........    /usr/bin/qemu-io (已被替换)
.........    /usr/bin/qemu-nbd (已被替换)
.........  d /usr/share/man/man1/qemu-img.1.gz (已被替换)
.........  d /usr/share/man/man8/qemu-nbd.8.gz (已被替换)

使用YUM来解决RPM包的依赖问题

   前面提到过,使用rpm命令来安装和卸载软件,处理RPM包之间的依赖关系非常复杂。YUM(Yellow dog Updater, Modified, YUM)很好地解决了软件包之间依赖关系地问题,在安装、升级、卸载RPM包的时候可以自动地将依赖包也一并安装或卸载。一般来说,在系统装好之后就可以直接使用yum命令了,不用进行额外地配置。如果想了解如何去创建和配置YUM,请参考YUM的文档(2)。

使用YUM来安装qemu-img:

[root@localhost ~]# yum install qemu-img
已加载插件:fastestmirror, ibm-repository, langpacks, refresh-packagekit
Loading mirror speeds from cached hostfile
 * fedora: mirrors.yun-idc.com
 * openclient: ocfedora.hursley.ibm.com
 * rpmfusion-free: mirror.bjtu.edu.cn
 * rpmfusion-free-updates: mirror.bjtu.edu.cn
 * rpmfusion-nonfree: mirror.bjtu.edu.cn
 * rpmfusion-nonfree-updates: mirror.bjtu.edu.cn
 * updates: mirrors.yun-idc.com
正在解决依赖关系
--> 正在检查事务
---> 软件包 qemu-img.i686.2.1.4.2-12.fc19 将被 安装
--> 解决依赖关系完成

依赖关系解决
    
    ========================================================================================================================================
     Package                        架构                       版本                                     源                             大小
    ========================================================================================================================================
    正在安装:
     qemu-img                       i686                       2:1.4.2-12.fc19                          updates                       454 k
    
    事务概要
    ========================================================================================================================================
    安装  1 软件包
    
    总下载量:454 k
    安装大小:2.0 M
    Is this ok [y/d/N]: y
    Downloading packages:
    Not downloading Presto metadata for updates
    qemu-img-1.4.2-12.fc19.i686.rpm                                                                                  | 454 kB  00:00:00     
    Running transaction check
    Running transaction test
    Transaction test succeeded
    Running transaction
      正在安装    : 2:qemu-img-1.4.2-12.fc19.i686                                                                                       1/1 
      验证中      : 2:qemu-img-1.4.2-12.fc19.i686                                                                                       1/1 
    
    已安装:
      qemu-img.i686 2:1.4.2-12.fc19                                                                                                         
    
    完毕!

可以看到,在安装qemu-img的时候,yum install 后面只需要接包名即可,无需完整的RPM文件名,而且YUM也会自动的解决安装包的依赖关系。

使用YUM来删除qemu-img:

[root@localhost ~]# yum remove qemu-img
已加载插件:fastestmirror, ibm-repository, langpacks, refresh-packagekit
正在解决依赖关系
--> 正在检查事务
---> 软件包 qemu-img.i686.2.1.4.2-12.fc19 将被 删除
--> 正在处理依赖关系 /usr/bin/qemu-img,它被软件包 libvirt-daemon-1.0.5.6-3.fc19.i686 需要
--> 正在使用新的信息重新解决依赖关系
--> 正在检查事务
---> 软件包 libvirt-daemon.i686.0.1.0.5.6-3.fc19 将被 删除
--> 正在处理依赖关系 libvirt-daemon = 1.0.5.6-3.fc19,它被软件包 libvirt-daemon-driver-interface-1.0.5.6-3.fc19.i686 需要
--> 正在处理依赖关系 libvirt-daemon = 1.0.5.6-3.fc19,它被软件包 libvirt-daemon-driver-nodedev-1.0.5.6-3.fc19.i686 需要
--> 正在处理依赖关系 libvirt-daemon = 1.0.5.6-3.fc19,它被软件包 libvirt-daemon-driver-uml-1.0.5.6-3.fc19.i686 需要
--> 正在处理依赖关系 libvirt-daemon = 1.0.5.6-3.fc19,它被软件包 libvirt-daemon-driver-nwfilter-1.0.5.6-3.fc19.i686 需要
--> 正在处理依赖关系 libvirt-daemon = 1.0.5.6-3.fc19,它被软件包 libvirt-1.0.5.6-3.fc19.i686 需要
--> 正在处理依赖关系 libvirt-daemon = 1.0.5.6-3.fc19,它被软件包 libvirt-daemon-driver-secret-1.0.5.6-3.fc19.i686 需要
--> 正在处理依赖关系 libvirt-daemon = 1.0.5.6-3.fc19,它被软件包 libvirt-daemon-driver-network-1.0.5.6-3.fc19.i686 需要
--> 正在处理依赖关系 libvirt-daemon = 1.0.5.6-3.fc19,它被软件包 libvirt-daemon-driver-lxc-1.0.5.6-3.fc19.i686 需要
--> 正在处理依赖关系 libvirt-daemon = 1.0.5.6-3.fc19,它被软件包 libvirt-daemon-config-nwfilter-1.0.5.6-3.fc19.i686 需要
--> 正在处理依赖关系 libvirt-daemon = 1.0.5.6-3.fc19,它被软件包 libvirt-daemon-driver-storage-1.0.5.6-3.fc19.i686 需要
--> 正在处理依赖关系 libvirt-daemon = 1.0.5.6-3.fc19,它被软件包 libvirt-daemon-driver-libxl-1.0.5.6-3.fc19.i686 需要
--> 正在处理依赖关系 libvirt-daemon = 1.0.5.6-3.fc19,它被软件包 libvirt-daemon-config-network-1.0.5.6-3.fc19.i686 需要
--> 正在处理依赖关系 libvirt-daemon = 1.0.5.6-3.fc19,它被软件包 libvirt-daemon-driver-qemu-1.0.5.6-3.fc19.i686 需要
--> 正在处理依赖关系 libvirt-daemon = 1.0.5.6-3.fc19,它被软件包 libvirt-daemon-driver-xen-1.0.5.6-3.fc19.i686 需要
--> 正在检查事务
---> 软件包 libvirt.i686.0.1.0.5.6-3.fc19 将被 删除
---> 软件包 libvirt-daemon-config-network.i686.0.1.0.5.6-3.fc19 将被 删除
---> 软件包 libvirt-daemon-config-nwfilter.i686.0.1.0.5.6-3.fc19 将被 删除
---> 软件包 libvirt-daemon-driver-interface.i686.0.1.0.5.6-3.fc19 将被 删除
---> 软件包 libvirt-daemon-driver-libxl.i686.0.1.0.5.6-3.fc19 将被 删除
---> 软件包 libvirt-daemon-driver-lxc.i686.0.1.0.5.6-3.fc19 将被 删除
---> 软件包 libvirt-daemon-driver-network.i686.0.1.0.5.6-3.fc19 将被 删除
---> 软件包 libvirt-daemon-driver-nodedev.i686.0.1.0.5.6-3.fc19 将被 删除
---> 软件包 libvirt-daemon-driver-nwfilter.i686.0.1.0.5.6-3.fc19 将被 删除
---> 软件包 libvirt-daemon-driver-qemu.i686.0.1.0.5.6-3.fc19 将被 删除
......
0:1.0.5.6-3.fc19              
      libvirt-daemon-driver-interface.i686 0:1.0.5.6-3.fc19               libvirt-daemon-driver-libxl.i686 0:1.0.5.6-3.fc19                 
      libvirt-daemon-driver-lxc.i686 0:1.0.5.6-3.fc19                     libvirt-daemon-driver-network.i686 0:1.0.5.6-3.fc19               
      libvirt-daemon-driver-nodedev.i686 0:1.0.5.6-3.fc19                 libvirt-daemon-driver-nwfilter.i686 0:1.0.5.6-3.fc19              
      libvirt-daemon-driver-qemu.i686 0:1.0.5.6-3.fc19                    libvirt-daemon-driver-secret.i686 0:1.0.5.6-3.fc19                
      libvirt-daemon-driver-storage.i686 0:1.0.5.6-3.fc19                 libvirt-daemon-driver-uml.i686 0:1.0.5.6-3.fc19                   
      libvirt-daemon-driver-xen.i686 0:1.0.5.6-3.fc19                    

完毕!

YUM在删除qemu-img的同时,也会将依赖于qemu-img的libvirt删除掉,这样便不会引起因依赖包的缺失而导致的软件无法使用的问题。

使用YUM来升级qemu-img:

[root@localhost ~]# yum update qemu-img
已加载插件:fastestmirror, ibm-repository, langpacks, refresh-packagekit
Loading mirror speeds from cached hostfile
 * fedora: mirrors.yun-idc.com
 * openclient: ocfedora.hursley.ibm.com
 * rpmfusion-free: mirror.bjtu.edu.cn
 * rpmfusion-free-updates: mirror.bjtu.edu.cn
 * rpmfusion-nonfree: mirror.bjtu.edu.cn
 * rpmfusion-nonfree-updates: mirror.bjtu.edu.cn
 * updates: mirrors.yun-idc.com
No packages marked for update

qemu-img已经是最新版本。

使用YUM来查看qemu-img的信息:

[root@localhost ~]# yum info qemu-img
已加载插件:fastestmirror, ibm-repository, langpacks, refresh-packagekit
Loading mirror speeds from cached hostfile
 * fedora: mirrors.yun-idc.com
 * openclient: ocfedora.hursley.ibm.com
 * rpmfusion-free: mirror.bjtu.edu.cn
 * rpmfusion-free-updates: mirror.bjtu.edu.cn
 * rpmfusion-nonfree: mirror.bjtu.edu.cn
 * rpmfusion-nonfree-updates: mirror.bjtu.edu.cn
 * updates: mirrors.yun-idc.com
可安装的软件包
名称    :qemu-img
架构    :i686
时期       :2
版本    :1.4.2
发布    :12.fc19
大小    :454 k
源    :updates/19/i386
简介    : QEMU command line tool for manipulating disk images
网址    :http://www.qemu.org/
协议    : GPLv2+ and LGPLv2+ and BSD
描述    : This package provides a command line tool for manipulating disk images

YUM虽然好用,但毕竟是建立在RPM机制之上的,所以rpm命令也至关重要。

制作自己的rpm包

   既然使用RPM包管理机制能够很便捷地安装、升级以及卸载软件,那么该如何把自己的软件制作成RPM包呢?其实步骤很简单,只需要编辑自己的spec文件,然后使用rpmbuild命令来打包即可。spec文件告诉了rpmbuild究竟要制作什么样的软件包(包名,版本,作者等),从哪获源代码,如何编译等。rpmbuild根据spec来制作满足需求的RPM包。所以,对于RPM打包来说,编写spec文件非常重要。如果想要很全面地学习spec文件的语法,可以阅读参考文献(3)和(4),本文只是以制作开源项目HLFS(5)的RPM包为例,来说明如何打包。读者并不需要深入的了解HLFS,只需要知道HLFS是用c语言所写,依赖于glib、log4c等函数库,编译用到cmake即可。

1.获取HLFS源代码

   在安装rpmbuild之后,通常会在家目录下产生rpmbuild目录:

[kelvin@localhost ~]$ ls -r ~/rpmbuild
SRPMS  SPECS  SOURCES  RPMS  BUILD

   这几个子目录中,SPECS目录用来放置spec文件,SOURCES目录用来放置软件的源代码,RPMS放置打包生成的RPM包,SRPMS放置生成的SRPM包(包的内容是源代码和spec文件),BUILD用来存放rpmbuild打包过程中临时用到的数据。

   在打包之前,需要把源代码先放到SOURCES目录下:

[kelvin@localhost ~]$ cd ~/rpmbuild/SOURCES/
[kelvin@localhost SOURCES]$ wget http://cloudxy.googlecode.com/files/hlfs.tar.gz

   源码拷贝之后需要在系统上安装编译HLFS所需要的工具cmake, glib等:

[kelvin@localhost ~]$ sudo yum install cmake glib2-devel log4c-devel snappy-devel java-1.8.0-openjdk-devel hadoop-0.20-libhdfs 

2.编辑hlfs.spec

   在SPECS目录下创建hlfs.spec并写入以下内容:

%define version 1.0

Name: 	hlfs
Version: %{version}
Release:	1
Summary: 	A distribuited and log-structured file system based on HDFS.

Group: 	Development/Libraries	
License: GPLv2	
URL: 	https://code.google.com/p/cloudxy		
Source0: 	hlfs-%{version}.tar.gz	

Buildroot: 	/tmp/hlfs
Requires: glib2 >= 2.0
Requires: log4c >= 1.2.3
Requires: snappy >= 1.1.0
Requires: java-1.8.0-openjdk-devel >= 1.8.0.0
Requires: hadoop-0.20-libhdfs >= 0.20.2
BuildRequires: cmake
BuildRequires: glib2-devel >= 2.0
BuildRequires: log4c-devel >= 1.2.3
BuildRequires: snappy-devel >= 1.1.0
BuildRequires: java-1.8.0-openjdk-devel >= 1.8.0.0
BuildRequires: hadoop-0.20-libhdfs >= 0.20.2

%description

HLFS(actually, Block level Storage System seems more proper than File System) 
is a distributed VM image storage system for ECMS,which provides highly availab-
le block level storage volumes that can be attached to XEN virtual machines by 
its tapdisk driver.Similar project related to KVM is sheepdog,but they are in 
different architectures.

Compared with sheepdog, HLFS has greater scalability and reliability than sheep-
dog by now,as we are on the shoulder of hadoop distribute file system (HDFS).Me-
anwhile,HLFS also supports advanced volume management features such as snapshot(
HLFS can also support snapshot tree)、cloning、thin provisioning and cache.

The main idea of HLFS is:

Take advantage of Log-structured File System's ideology to build an on-line ima-
ge storage system on HDFS which can guarantee the reliability and scalability 
for our storage system
The ideology of LFS makes our storage system support random access to online im-
ages.The ideology of LFS also makes our storage system more efficient and easily
take snapshot.

%prep
%setup -q

%build
cd ./build
cmake ../src
make

%install
cd build
make install DESTDIR=%{buildroot}


%files
%dir /usr/local/hlfs/bin
%dir /usr/local/hlfs/include
%dir /usr/local/hlfs/lib
%dir /usr/local/hlfs
/usr/local/hlfs/bin/clone.hlfs
/usr/local/hlfs/bin/log4crc
/usr/local/hlfs/bin/mkfs.hlfs
/usr/local/hlfs/bin/nbd_ops
/usr/local/hlfs/bin/rmfs.hlfs
/usr/local/hlfs/bin/segcalc.hlfs
/usr/local/hlfs/bin/segclean.hlfs
/usr/local/hlfs/bin/snapshot.hlfs
/usr/local/hlfs/bin/stat.hlfs
/usr/local/hlfs/bin/tapdisk_ops
/usr/local/hlfs/include/address.h
/usr/local/hlfs/include/api/hlfs.h
/usr/local/hlfs/include/base_ops.h
/usr/local/hlfs/include/bs_hdfs.h
/usr/local/hlfs/include/bs_local.h
/usr/local/hlfs/include/cache.h
/usr/local/hlfs/include/cache_helper.h
/usr/local/hlfs/include/clone.h
/usr/local/hlfs/include/cmd_define.h
/usr/local/hlfs/include/comm_define.h
/usr/local/hlfs/include/ctrl_region.h
/usr/local/hlfs/include/hlfs_ctrl.h
/usr/local/hlfs/include/hlfs_log.h
/usr/local/hlfs/include/icache.h
/usr/local/hlfs/include/logger.h
/usr/local/hlfs/include/misc.h
/usr/local/hlfs/include/seg_clean.h
/usr/local/hlfs/include/seg_clean_helper.h
/usr/local/hlfs/include/snapshot.h
/usr/local/hlfs/include/snapshot_helper.h
/usr/local/hlfs/include/storage.h
/usr/local/hlfs/include/storage_helper.h
/usr/local/hlfs/lib/libhlfs.so

%clean
rm -rf $RPM_BUILD_ROOT

%changelog
* Sat Aug 10 2013 Wang Sen<wangsen@linux.vnet.ibm.com> 1.0-1
- First build.

   spec文件可以使用%define来定义宏,并且用%{name}来展开宏。文件开头将HLFS的版本号用宏来定义以方便以后的升级。从结构上来看,hlfs.spec大致分为以下几个部分:

%description及包信息
%prep
%build
%install
%files
%clean
%changelog

   第一部分给出了HLFS软件包的一些基本信息,比如说包名、版本号、release号、项目的URL、源代码等信息。一般来说,对于同一版本的代码,每打包一次release号加1。Group指定了该软件的类型,rpm所有可用的类型可以在/usr/share/doc/rpm-(rpm版本)/GROUPS文件中查到。summary和description给出了软件包的介绍,这些内容可以通过rpm -qi或者yum info来查看到。Requires指定了HLFS需要依赖的包及其版本号,BuildRequires指出了打包需要的软件包及其版本号。

   %prep部分的主要作用是将源代码解压缩解包以提供给编译过程,一般是通过%setup宏来实现。当然也可以不用这个宏,自己手动的实现。rpmbuild常用的宏是在以下几个文件中定义的,需要时可以查看:

/usr/lib/rpm/macros
/usr/lib/rpm/redhat/macros
/etc/rpm/macros
~/.rpmmacros

   %build部分是将上面准备好的源代码通过shell命令来编译成二进制可执行文件。

   %install将编译好的二进制文件安装到rpmbuild所用的临时表示最终安装根目录的buildroot里。

   %files指定HLFS的RPM包所要包含的文件和目录,可以通过rpm -qpl来查看到这些文件列表。

   %clean部分用来删除打包过程中产生的临时文件。    %changelog用来记录spec文件的改动,在vim的命令模式下可以输入\c来快速地插入作者信息及时间戳。

3. 使用rpmbuild打包

   编写完spec文件之后就可以使用rpmbuild命令来打包了。选项-bb用来产生二进制包;-bs用来产生SRPM包;-ba生成两种类型地包。我们使用rpmbuild -ba来生成二进制包和源码包:

[kelvin@localhost SPECS]$ rpmbuild -ba hlfs.spec 

   结果生成地二进制包会放到RPMS/${arch}目录下,源码包会放到SRPMS目录下。打包完成后可以通过rpm命令来检验所产生地RPM包:

[kelvin@localhost ~]$ cd ~/rpmbuild/RPMS/i386/
[kelvin@localhost i386]$ ls
hlfs-1.0-1.i386.rpm
[kelvin@localhost i386]$ rpm -qpl hlfs-1.0-1.i386.rpm 
/usr/local/hlfs
/usr/local/hlfs/bin
/usr/local/hlfs/bin/clone.hlfs
/usr/local/hlfs/bin/log4crc
/usr/local/hlfs/bin/mkfs.hlfs
/usr/local/hlfs/bin/nbd_ops
/usr/local/hlfs/bin/rmfs.hlfs
/usr/local/hlfs/bin/segcalc.hlfs
/usr/local/hlfs/bin/segclean.hlfs
/usr/local/hlfs/bin/snapshot.hlfs
/usr/local/hlfs/bin/stat.hlfs
/usr/local/hlfs/bin/tapdisk_ops
/usr/local/hlfs/include
/usr/local/hlfs/include/address.h
/usr/local/hlfs/include/api/hlfs.h
/usr/local/hlfs/include/base_ops.h
/usr/local/hlfs/include/bs_hdfs.h
/usr/local/hlfs/include/bs_local.h
/usr/local/hlfs/include/cache.h
/usr/local/hlfs/include/cache_helper.h
/usr/local/hlfs/include/clone.h
/usr/local/hlfs/include/cmd_define.h
/usr/local/hlfs/include/comm_define.h
/usr/local/hlfs/include/ctrl_region.h
/usr/local/hlfs/include/hlfs_ctrl.h
/usr/local/hlfs/include/hlfs_log.h
/usr/local/hlfs/include/icache.h
/usr/local/hlfs/include/logger.h
/usr/local/hlfs/include/misc.h
/usr/local/hlfs/include/seg_clean.h
/usr/local/hlfs/include/seg_clean_helper.h
/usr/local/hlfs/include/snapshot.h
/usr/local/hlfs/include/snapshot_helper.h
/usr/local/hlfs/include/storage.h
/usr/local/hlfs/include/storage_helper.h
/usr/local/hlfs/lib
/usr/local/hlfs/lib/libhlfs.so
[kelvin@localhost i386]$ rpm -qpi hlfs-1.0-1.i386.rpm 
Name        : hlfs
Version     : 1.0
Release     : 1
Architecture: i386
Install Date: (not installed)
Group       : Development/Libraries
Size        : 851341
License     : GPLv2
Signature   : (none)
Source RPM  : hlfs-1.0-1.src.rpm
Build Date  : 2013年11月04日 星期一 17时26分34秒
Build Host  : localhost.localdomain
Relocations : (not relocatable)
URL         : https://code.google.com/p/cloudxy
Summary     : A distribuited and log-structured file system based on HDFS.
Description :

HLFS(actually, Block level Storage System seems more proper than File System)
is a distributed VM image storage system for ECMS,which provides highly availab-
le block level storage volumes that can be attached to XEN virtual machines by
its tapdisk driver.Similar project related to KVM is sheepdog,but they are in
different architectures.

Compared with sheepdog, HLFS has greater scalability and reliability than sheep-
dog by now,as we are on the shoulder of hadoop distribute file system (HDFS).Me-
anwhile,HLFS also supports advanced volume management features such as snapshot(
HLFS can also support snapshot tree)、cloning、thin provisioning and cache.

The main idea of HLFS is:

Take advantage of Log-structured File System's ideology to build an on-line ima-
ge storage system on HDFS which can guarantee the reliability and scalability
for our storage system
The ideology of LFS makes our storage system support random access to online im-
ages.The ideology of LFS also makes our storage system more efficient and easily
take snapshot.
[kelvin@localhost i386]$ rpm -qpR hlfs-1.0-1.i386.rpm 
glib2 >= 2.0
hadoop-0.20-libhdfs >= 0.20.2
java-1.8.0-openjdk-devel >= 1.8.0.0
libc.so.6
libc.so.6(GLIBC_2.0)
libc.so.6(GLIBC_2.1)
libc.so.6(GLIBC_2.1.3)
libglib-2.0.so.0
libgthread-2.0.so.0
libhdfs.so.0
libjvm.so
liblog4c.so.3
librt.so.1
libsnappy.so.1
log4c >= 1.2.3
rpmlib(CompressedFileNames) <= 3.0.4-1
rpmlib(PayloadFilesHavePrefix) <= 4.0-1
rtld(GNU_HASH)
snappy >= 1.1.0

小结

   本文通过实例介绍了RPM包的使用以及打包方法,适合初学者快速地掌握RPM软件包机制,如果需要更加详细地了解RPM软件包机制,请阅读参考文献(4)和(5)。

参考文献

1. 鸟哥的Linux私房菜——基础学习篇,第三版,22章。
2. YUM文档:http://yum.baseurl.org/wiki/Guides
3. Fedora RPM Guide:http://docs.fedoraproject.org/en-US/Fedora_Draft_Documentation/0.1/html/RPM_Guide/index.html
4. Maximum RPM:http://ishare.iask.sina.com.cn/f/37848528.html
5. HLFS Project Home:http://code.google.com/p/cloudxy/