Archive四月 2014

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
    
    
    /**********************************************************************************/

Linux shell 程序设计5——shell中一些特殊符号的用法总结

1、{} 大括号:

eg: ls my_{finger,toe}s

这条命令相当于如下两个命令的组合:

ls my_fingers ; ls my_toes

eg: mkdir {userA,userB,userC}-{home,bin,data}

我们得到 userA-home, userA-bin, userA-data, userB-home, userB-bin, userB-data, userC-home, userC-bin, userC-data,这几个目录

大括号也可用于语句块的构造,命令之间可用回车隔开

2、[] 中括号:允许匹配方括号中任何一个单个字符

eg: ls /[eh][to][cm]*

相当于执行 ls /etc 和 ls /home。

常出现在流程控制中,其作用是括住判断式。注意:[、] 与表达式之间有空格。

eg:

if [ "$?" != 0 ]
then echo "Executes error"
fi

3、command 反引号:反引号中的指令将会被执行

eg: fdv=date +%F

在倒引号内的 date +%F 会被视为指令,执行的结果会带入 fdv 变量中

4、’string’ 单引号 和 “string” 双引号:如果想在定义的变量中加入空格,就必须使用单引号或双引号,单、双引号的区别在于双引号转义特殊字符,而单引号不转义特殊字符。

  eg: heyyou=home
      echo '$heyyou'
      # We get $heyyou
  eg: heyyou=home
      echo "$heyyou"
      # We get home

5、$#:它的作用是告诉你引用变量的总数量是多少

6、$$:它的作用是告诉你shell脚本的进程号

7、$1、$2、$3……${10}、${11}、${12}…… :表示脚本的各个参数

8、$@:列出所有的参数,各参数用空格隔开

9、AND列表 statement1 && statement2 && statement3 && ……:只有在前面所有的命令都执行成功的情况下才执行后一条命令

10、OR列表 statement1 || statement2 || statement3 || ……:允许执行一系列命令直到有一条命令成功为止,其后所有命令将不再被执行。

11、[ condition ] && command for true || command for false:当条件为真时,执行command for true ,当条件为假时,执行command for false

12、: 冒号:内建指令,但返回值为0

  eg: :
      echo $?
      #We get 0
while:实现一个无限循环

13、; 分号:在 shell 中,担任”连续指令”功能的符号就是”分号”

eg:cd ~/backup ; mkdir startup ; cp ~/.* startup/.

14、~:代表使用者的 home 目录

15、# 井号:表示符号后面的是注解文字,不会被执行

16、\ 倒斜线:放在指令前,有取消 aliases 的作用;放在特殊符号前,则该特殊符号的作用消失;放在指令的最末端,表示指令连接下一行

17、! 惊叹号:通常它代表反逻辑的作用,譬如条件侦测中,用 != 来代表”不等于”

18、* 星号:在文件名扩展上,她用来代表任何字元

19、** 次方运算:两个星号在运算时代表 “次方” 的意思

  eg:let "sus=2**3"
     echo "sus = $sus"
     # sus = 8

Linux shell 程序设计4——shell变量

1、shell变量没有类型,所有变量都被当作字符串来处理。

2、shell变量的命名和c语言相同。

3、shell变量赋值和c语言略有不同,shell赋值要求等号的两边不能出现空格,而在linux C 中,一般为了增强代码的可读性,等号的两边都加一个空格。如果shell变量的赋值为字符串,而且字符串中含有空格,则必须给该字符串加单引号或双引号。

4、shell变量不同于c语言,无需定义可直接赋值使用。例如:

#!/bin/bash
#This is an example to show how to use variables

version="2.6.24"
name="linux-headers-2.6.24"

echo -e "name:$name\nversion:$version"

执行结果:

name:linux-headers-2.6.24
version:2.6.24

5、shell变量的作用范围是本shell环境。例如:

我们编写如下脚本:

#!/bin/bash
#script name: exam.sh
#This is a example to show the action range of a variable

os_name=linux
echo $os_name

当我们使用./exam.sh执行脚本结果为:

linux

然后我们在命令行中执行:

echo $os_name 结果为空

而如果使用 source exam.sh 执行脚本或者是 .空格exam.sh命令执行脚本后键入echo $os_name 命令,我们会得到:

linux

6、有一种能继承给子shell的变量,称之为环境变量。让一个变量变身为环境变量的方法为:

export 变量名

例如:在终端中我们敲入如下命令:

执行脚本:

#!/bin/bash
echo $a

我们什么也不能得到。而如果在终端中使用命令:

export a=linux

然后执行上述脚本,我们的到结果:

linux

7、shell内置变量:bash设置了许多内置变量,在进行shell程序设计的时候可能需要用到。详见:

$?:最后一次执行的命令的返回码
$$:shell进程自己的PID
$!:shell进程最近启动的后台进程PID
$#:shell脚本参数个数,不含脚本名
$0:脚本文件本身的名字
$1、$2...:第一个参数、第二个参数...
$*:代表所有的参数(不含脚本名)组成的字符串
$@:命令行参数组成的多个字符串,每个参数对应一个

8、设置shell变量属性:

readonly:使用readonly命令可以

Linux shell 程序设计3——命令行程序

1、date:显示、设置系统的日期和时间。

$date
2011年 01月 30日 星期日 14:43:35 CST
$date 012309232011
执行结果:设置主机的时间日期为:2011/01/23 09:23

$date  +'%d %H %M'

以一定的格式显示时间或日期信息,常见有如下的格式:

%d   :显示日
$date +'%d'
30
%D  :显示日期
$date +'%D'
01/30/11
%e  :不足二位数的部分不用0补而是用空格补
%m :显示月份
%Y  :显示公元年
$date +'%Y'
2011
%m  :显示月
%M  :显示分钟
%S   :显示秒
%s   :显示自1970年1月1日 00:00:00开始到现在的秒数
$date +'%s'
1296371799
$date -R :产生与RFC-2822兼容的时间字符串
$date -R
Sun, 30 Jan 2011 15:20:17 +0800

2、cat:连接文件并显示

cat file1
显示file1文件的内容
cat file1 file2
将file1 和 file2并将结果显示
cat file1 file2>result.txt
将file1和file2连接并将结果重定向到result.txt

3、wc:计算文件内含的总字数或行数

wc -l file  :显示文件file的行数
wc -c file :显示文件file的字符
wc -w file :显示文件file的单词个数

4、find :在分层目录中寻找文件。

find 路径 样式 操作

例如:

find /  -name  *.txt -print

/为路径,-name *.txt是样式 ,-print是操作, 表示将结果打印到标准输出。

find / -name *.txt -exec rm-f {} \;

-exec的操作表示找到所有的.txt文件之后 执行 rm -f命令,{}表示找到的所有结果,;是-exec的终止符,由于;是shell的特殊符号,所有要用\将其转义。

find /etc -cnewer /etc/passwd

在/etc 目录中寻找比/etc/passwd文件异动时间新的文件

find /etc -type d -print

将/etc目录中所有类型为d(也就是目录)的文件打印出来

其他的类型还有:

b:块设备文件 c:字符设备文件 d:目录 p:管道 f:一般文件 l:链接文件 s:socket

5、basename:取得路径名称中最后的文件名部分

如:

basename /etc/configure.sh

执行结果:configure.sh

6、dirname:取得路径中目录部分

如:

dirname /etc/configure.sh

执行结果:/etc

7、sort:按ascii码的行首字母对文件的行做排序

sort file1  :按ascii码值增大的顺序
sort -r file1:按ascii码值减少的顺序
sort -n file1:按字符串比较
sort -k 2 file1:按字符串比较每行的第二个字段
sort -nk 2 file1:按数值比较每行的第二个字段
sort -nr +2 -t: /etc/passwd :+2表示跳过前两个字段,-t: 表示该:为字段分隔符

8、uniq:删除重复行,若重复行没相邻,则无作用

uniq -d: 挑出重复行 uniq -c: 计算每一行的重复次数

如:

编辑文件q,文件内容如下:

baaaaaaaaaa
baaaaaaaaaa
baaaaaaaaaa
baaaaaaaaaa
aaaaaaaaaaa
aaaaaaaaaaa
dddddddddd
dddddddddd
dddddddddd
dddddddddd
dddddddddd
dddddddddd

执行命令uniq -d q结果为:

baaaaaaaaaa
aaaaaaaaaaa
dddddddddd

执行命令uniq -c q结果为:

4 baaaaaaaaaa
2 aaaaaaaaaaa
6 dddddddddd

该命令和sort命令一样,都不改变原文件内容,若要保存结果,可通过重定向和管道。

9、cut :从文件中抽出某一部分

如:

cut -c2 q :从文件q中抽出每一行的第2个字符
cut -c2-10 q:从文件中抽出每一行的第2到第10个字符
cut -c2- q:从q中抽出每一行第2个及其以后的字符
cut -d: -f3,4 passwd:从文件passwd中抽出每一行的第3个和第4个字段,-d:表明:为分割符

10、paste:把两个文件按行合并,默认以Tab分割

paste -d'#' file1 file2:以#分割
paste -s file:file的每一行和自己的每一行合并

11、tr:转换和删除字符。

如:

$tr k K < file1

将file1中所有的k换成K

$tr -d k <file1

将file1中所有的k删除

$tr '[A-Z]' '[a-z]' <file1

将file1中所有的大写字母换成小写字母

$cut -d: -f1-6 /etc/passwd |tr :‘+’

将passwd文件中前六个字段中的分隔符用+代替

12、grep:显示符合样式的行

grep A * :将含有A这个字符的文件及行打印出来
grep -i A * :-i 表示不区分大小写,A或a都行
grep -v A file :将file中所有不包含A的行打印出来
grep -l teacher *:只显示含有teacher的文件的文件名而不显示具体的行
grep -n teacher *:显示文件名和行号
grep -q teacher filename :若filename文件中含有teacher关键字则返回0,否则返回非0
grep -A 200 -e 'wadfadfdf' filename :表示在filename 中查找wadfadfdf行并显示其后的200行 

13、 tee:从标准输入读取数据,显示在标准输出上,并将内容写在指定的文件中。

$tee filename

若filename已经存在,则清空其内容,否则新建一个文件。按ctrl+D组合键,输入的数据就存储在filename中。

$tee -a filename 以追加的方式写入文件

14、diff:比较两个文件之间的差异

15、comm:以列和列的方式比较两个已排序好的文件

如:

file1 文件的内容如下:

1 2 3
6 5 4
9 8 7
a b c 
file2:
2 4 5
6 5 4
8 0 9 
x y z

执行comm file1 file2后的结果:

1 2 3
2 4 5
6 5 4
8 0 9   
9 8 7
a b c
x y z

第1列为file1与file2不同的内容,第2列为file2与file1不同的内容,第3列为file1和file2相同的内容。

16、xargs:安排标准输入给要执行命令的参数

如:

$find . -name *.txt | xargs -n 2 diff

将找到的.txt文件以两个一组的方式交给diff进行比较

17、按以下格式可执行多个命令:

A、命令1;命令2;命令3...	 执行一组命令,不能保证每个命令都成功执行
B、命令1&&命令2&&命令3... 	  依次执行命令1、命令2...直到执行失败
C、命令1||命令2||命令3...       依次执行命令1、命令2...直到执行成功
D、(命令1;命令2;...)  开启一个子shell去执行该组命令 
E、{  命令1;命令2;...  } 在现行的shell中执行该组命令,{右和}左有至少一个空格

18、script::记录命令执行内容。

$script com.log
$ls 
$exit

ls命令的执行结果会被被保存在com.log中

Linux shell 程序设计2——bash的内置命令

常用的内置命令忽略,来看看shell编程中其他一些重要的内置命令:

1、help:显示所有内置命令列表,或显示一个具体命令的用法。

-s: 表示列出命令的语法格式

例子:

help -s help help: help [-dms] [pattern …]

2、echo:用来显示一行文字。默认自动换行。

-n:取消自动换行。
-e:让字符串中的特殊字符起作用,即使字符串在单引号中。

例子: echo hello world 或 echo ‘hello world’ 或 echo “hello world” 输出结果为:hello world $echo -e “hello \n world” hello world $ echo -e ‘hello \n world’ hello world

3、printf:显示格式字符串,类似于c中的printf函数

格式:printf “格式字符串” 参数

-v :不显示到标准输出,而是赋值给-v选项后面的变量

例子:

str= "hello world"
printf "%s\n" "$str"     执行结果:hello world

printf -v str "hello world" 
echo $str                      执行结果:hello world

printf "%q" "hello world \n" 执行结果:hello\ world\\n 无换行

%q这个选项我想了很久才弄明白它的含义:

将字符串或变量中的转义字符 用 源码格式替换。于是,上面的例子中,空格变成了\空格,\变成了\,而且\n没有了换行的作用

printf详细用法请参考博客:

http://bbs.chinaunix.net/thread-845520-1-1.html

4、: 什么也不做,返回0

例子:

:
echo $?                    运行结果:0

5、. 或 source :在现行shell中执行shell程序

例子:

编辑脚本文件a_var.sh:

#!/bin/bash
a=31

保存退出后回到终端,修改a_var.sh的权限并输入命令:

./a_var.sh

执行,然后在终端执行:

echo $a

输出结果为一个空行,意味着变量a的值为空,我们再以命令.空格a_var.sh 或source a_var.sh 执行,然后输入:

echo $a                     其输出结果为:  31

第一种方式执行a_var.sh,bash会创建一个shell去执行,当子shell执行完成后,它的变量a会被系统收回。

6、alias:显示或设定程序别名

例子:

alias          执行结果:列出所有的别名
alias ll='ls -al'  
ll               执行结果:等价于执行了  ls -al

7、unalias:取消别名

alias ll

8、exit :离开脚本或登录shell,可带返回值

exit 1

9、history:显示过去曾经执行过的shell指令,与history命令相关的有三个重要的变量:

HISTFILE :记录存放历史命令文件的路径,如:

echo $HISTFILE                    结果为:/home/kelvin/.bash_history

HISTFILESIZE:设置历史命令文件命令的最大个数,超过这个个数,序号在前的命令记录就会被删除

HISTSIZE:设置终端中交互式命令的历史记录个数。它和HISTFILESIZE相比的最小值起作用。

10、fc:列出登录主机后最近执行过的命令。一般和选项 -l 配合使用。

例子:

$fc -l      结果:

363	 cat /etc/profile
364	 echo $HISTORY
365	 echo $HISTORYFILE
366	 echo $HISTFILE
367	 ehco $HISTFILESIZE
368	 echo $HISTFILESIZE
369	 echo $HISTSIZE
370	 history 
371	 history
372	 echo $HISTFILE
373	 lw
374	 ls
375	 fc -l
376	 fc -l 368
377	 fc -l echo l
378	 fc -l
fc -l 375     列出375行以后的命令                输出结果:
375	 fc -l
376	 fc -l 368
377	 fc -l echo l
378	 fc -l
fc -l 375 377 列出375到377之间的命令          输出结果:
375	 fc -l
376	 fc -l 368
377	 fc -l echo l
fc -l echo  l    列出从 关键字 echo 到 l之间的内容     输出结果:
372	 echo $HISTFILE
373	 lw
374	 ls

11、type:对一个命令的类型进行说明(包含命令行程序)。

例子:

$type ls

ls 已被别名为“ls --color=auto”

$type cp

cp 是 /bin/cp

$type fc

fc 是一个 shell 内部命令

12、set:列出所有变量和函数的内容,加入选项可以设置bash的某个属性是否打开

例子:

$set -o 查看所有属性,或打开某个属性

allexport      	off
braceexpand    	on
emacs          	on
errexit        	off
errtrace       	off
functrace      	off
hashall        	on
histexpand     	on
history        	on
ignoreeof      	off
interactive-comments	on
keyword        	off
monitor        	on
noclobber      	off
noexec         	off
noglob         	off
nolog          	off
notify         	off
nounset        	off
onecmd         	off
physical       	off
pipefail       	off
posix          	off
privileged     	off
verbose        	off
vi             	off
xtrace         	off

$set -o notify
set -o              打开notify属性后显示所有属性状态,输出结果:
allexport      	off
braceexpand    	on    
emacs          	on
errexit        	off
errtrace       	off
unctrace      	off
hashall        	on
histexpand     	on
history        	on
ignoreeof      	off
interactive-comments	on
keyword        	off
monitor        	on
noclobber      	off
noexec         	off
noglob         	off
nolog          	off
notify         	on
nounset        	off
onecmd         	off
physical       	off
pipef    ail       	off
posix          	off
privileged     	off    
verbose        	off
vi             	off
xtrace         	off

set  +o notify 
set -o         关闭notify属性,并显示所有属性状态:
allexport      	off
braceexpand    	on
emacs          	on
errexit        	off
errtrace       	off
functrace      	off
hashall        	on
histexpand     	on
history        	on
ignoreeof      	off
interactive-comments	on
keyword        	off
monitor        	on
noclobber      	off
noexec         	off
noglob         	off
nolog          	off
notify         	off
nounset        	off
onecmd         	off
physical       	off
pipefail       	off
posix          	off
privileged     	off
verbose        	off
vi             	off
xtrace         	off

set -C 或 set -o noclobber :保护已存在文件,不让重定向覆盖文件内容,只能追加。

例如:

set -C 
touch a.c
echo adfad >  a.c  提示出错:
bash: a.c:无法覆盖已经存在的文件

但当我们追加内容时不会提示出错:

echo adfasf >> a.c

可用set +C 取消

set -u:用于测试变量是否存在

例如:

: $i

echo $? 这儿的返回值应该为1,因为变量i不存在

i=1
: $i

echo $? 这儿的输出结果应该是0。同样,可以用set +u取消作用

set -v:显示当前shell的每一个执行命令,换句话说,就是把执行的命令打印出来

例如:

kelvin@kelvin-Founder:~$ set -v
kelvin@kelvin-Founder:~$ ls
ls
a.c  Linux material  project_files  record  shell  software  桌面

可用于对shell脚本的排错,该属性可用set +v取消作用

13、shopt:很多方面都和set命令一样,但它增加了很多选项。

-s:开启选项
-u:关闭选项
-o:set -o
-q:以返回值的形式表示开关状态,非0表示关,0表示开

set和shopt 的细节参见:

http://blogold.chinaunix.net/u3/94271/showart.php?id=2195391

14、read:从标准输入读取一行数据

例子:

#!/bin/bash
echo "please input your name "
read your_name                             //如果不输入your_name,读取结果会默认存入变量ERPLY
echo "your name is :"  $your_name   

执行结果:

please in put your name 
kelvin
your name is : kelvin

read -p “提示信息” ;所以上述sh脚本也可写成:

#!/bin/bash
read -p "please input your name" your_name
echo "your name is:" $your_name

read -a arr:将一行数据存入数组arr

例如:

read -a arr <<(echo 1 2 343 23) 

这样,echo ${arr[2]} 的输出结果就是343

read还可以读值给多个变量:

IFS=':'
read f1 f2 f3 f4 f5 f6 f7 < /etc/passwd

因为passwd中7个字段是由:分割开的,所以令IFS=’:’

15、time:打印设置命令执行的real user sys时间,real 表示命令真正运行时间,cpu使用时间由两部分表示: user表示用户态程序执行时间, sys 表示系统调用时间。

例如:

time ls
time ls
adf.sh  a_var.sh  name.sh

real	0m0.004s
user	0m0.000s
sys	0m0.000s

16、exec:后接命令或程序,执行命令或程序,并取代原来的shell执行环境;执行重定向生效,例如:

exec < file 那么凡是由标准输入读入数据的操作都改为由file读入数据

17、eval:读取变量,并将变量的内容作为命令执行

例如:

listlog="ls -al /var/log/*.log"
eval $listlog

执行结果:ls -al /var/log/*.log将被执行。

Linux shell 程序设计1——安装及入门

1、什么是shell?

shell是linux内核的“壳”,是用户和内核的桥梁。它类似于windows下的命令提示符,将用户输入的命令解释给内核执行,并返回给用户结果。与windows命令提示符不同的是,shell还是一种脚本语言,可以按一定的流程将命令组合在一起使用,方便了用户。

2、shell的安装:

在ftp.gnu.org/gnu/bash可下载到bash的源码包:

bash-4.1.tar.gz

用 tar xzvf bash-4.1.tar.gz 解压,生成bash-4.1目录

使用cd 命令进入该目录,在该目录下执行./configure命令生成配置文件,再使用make命令编译,使用make install命令安装。

在/etc/shells文件中列出的shell才是合法的shell,所以要使用安装的shell必须把它加到该文件中。加入之后就可以通过chsh命令来切换shell。

3、shell中的特殊符号:

http://blogold.chinaunix.net/u2/75431/showart_1110962.html

4、shell的程序结构:

以#!开头,指名要解释、执行该脚本的shell,如:

#! /bin/bash

其余以#开头的行为注释。除此之外,一个shell脚本还包括变量设定、内置命令、函数、以及流程控制语句。

下面是一个简单的shell脚本:

  1 #/bin/bash
  2 #This is a test shell script
  3 #It's function is show how to use the function of a shell script
   
  4 /*定义了一个函数,其中$1,$2,$3是传递给该函数的参数*/
  5 function show(){
  6     echo "Today is $1.Your name is $2,and your ip address is $3."       
  7 }
  
  8 /*定义了三个变量*/
  9 name="$1"
 10 date=`date +%F`
 11 ip="222.24.19.12"
 
 12 /*$#为执行shell脚本时传递给该脚本的参数的个数,脚本名不计*/ 
 13 if [ $# != 1 ]; then
 14     echo "input error!"
 15     exit
 16 fi

 17 /*调用上面定义的函数*/
 18 show "$date" "$name" "$ip"
 19 
 20 sleep 5
 21 echo     //输出一个空行
 22 echo "exited!"

5、shell脚本排错:

在执行shell脚本之前,需要修改该脚本的权限:

chmod 755  脚本名

可以用两种方式执行该脚本:

  • ./脚本名 参数 或 bash 脚本名 参数 以这种方式执行一个shell脚本,bash会创建一个子shell来执行,所用的环境是子shell的执行环境,当执行结束后又会回到父shell的执行环境。

  • . /脚本名 参数 或 source 脚本名 参数 以这种方式执行的shell脚本,bash不会创建子shell,而是在自己的环境中执行,执行完成后,若脚本中有修改环境的地方,则bash的环境就会改变。

shell脚本由于是脚本程序,无需编译,所以排错只能依靠阅读源码排错或者是使用 bash -x 脚本名 参数 的执行方式追踪脚本的执行过程

6、shell脚本执行原理:

用户在登录之后,就会进入一个shell环境,称之为父shell,其他脚本执行时称之为子shell。每个用户都有一个默认的登录shell,保存在/etc/passwd文件中。用户可执行chsh修改默认的登录shell。子shell会继承父shell的环境变量。子shell也可以使用 bash命令再创建一个子shell,使用exit 退出一个shell。使用echo $SHLVL可以查看位于第几层shell中。

7、bash的启动配置文件:

用户登录时,login shell 会读取/etc/profile并执行,接着检查用户家目录中是否有.bash_profile,有则执行,然后检查是否有.bash_login ,有则执行,最后检查.profile,有则执行。注销的时候,bash会检查用户家目录中是否有.bash_logout,有则执行。 在执行一个新的shell时,若执行的是交互式shell,或者叫做命令,bash会检查并执行/etc/bash.bashrc以及家目录中的.bashrc。若执行的脚本,则检查BASH_ENV变量,并执行该变量所指向的文件。

Shell常用命令小结

1、ls:这是linux里最常用的命令,像数学里的1一样,简单但很重要。类似于dos里的dir命令,该命令的功能是列出目录下的文件或子目录。

-a:显示所有文件和目录,包括以.开头的隐藏文件
-l:以长格式的形式显示

例如:

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

kelvin@kelvin-laptop:~$ ls -a
.                 .gconfd          .pulse-cookie
..                .gksu.lock       .recently-used.xbel
.bash_history     .gnash           .sudo_as_admin_successful
.bash_logout      .gnome2          .themes
.bashrc           .gnome2_private  .thumbnails
.bogofilter       .gstreamer-0.10  .update-notifier
.cache            .gtk-bookmarks   .xsession-errors
.compiz           .gvfs            .xsession-errors.old
.config           .ICEauthority    公共的
.dbus             .icons           模板
.dmrc             .local           视频
.esd_auth         .mozilla         图片
.evolution        .nautilus        文档
examples.desktop  .openoffice.org  下载
.fontconfig       .profile         音乐
.gconf            .pulse           桌面

kelvin@kelvin-laptop:~$ ls -l
总用量 36
-rw-r--r-- 1 kelvin kelvin  179 2010-09-14 09:39 examples.desktop
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-14 09:49 桌面

2、cd:选择当前工作目录。例如:

cd .. :回到上级目录。 cd 或 cd ~:返回家目录。 cd -:返回操作前目录

cd !$ :把上个命令的参数作为输入。

3、touch:创建一个或多个空文件。

4、pwd:显示当前工作目录。

5、cp:复制文件或目录

格式:cp 源 目的

-r:复制目录
-f:如遇同名文件或目录,强制覆盖目的文件或目录
-p:保留文件或目录创建日期。

6、rm:删除文件或目录

-r:如果是删除目录,需要加该选项。
-f:强制删除

7、mv:移动文件或目录,一般用于重命名。

8、cat:显示全部的文件内容。

9、more:也是显示文件内容,一次只显示一屏幕,按空格显示下一页,按回车显示下一行,按q键退出。

10、less:显示文件内容,和more类似,区别在于less命令显示的文件可用方向键或pgup或pgdown控制。

11、head:显示文件头几行。如:head -3:显示头三行,默认为10行。

12、tail:显示文件后几行,与head类似。

13、ln:创建文件或目录的链接。源与目的都要使用绝对路径,且硬链接不能跨越磁盘分区。-s:创建软链接

14、mkdir:创建空目录。-p:可创建子目录。rmdir:删除空目录。-p:强制删除非空目录。

15、whereis:查看命令以及手册的位置。如:

kelvin@kelvin-laptop:~$ whereis ls
ls: /bin/ls /usr/share/man/man1/ls.1.gz

16、whatis:查看命令功能。

17、man:查看命令手册

18、find:查找文件或目录。例如:find /etc -name f*

19、locate:查找文件或目录。无需路径。需要注意的是,对于新创建的文件或目录,用updatedb更新数据库后才能用locate命令找到。

20、grep:查找文件中的内容。格式:grep 关键字 文件名

21、gzip:压缩文件。

-1:快速压缩 -9:最佳压缩

22、gunzip:解压文件。=gzip -d

23、tar:打包文件。

  -c:创建一个包。-v:显示详细过程。 -f:指定包名  -x:解包

可用该命令压缩打包,或解压缩解包:

tar -zcvf file.tar.gz filename1 filename2 ……   
tar -zxvf file.tar.gz

插入法排序

何谓算法?算法就是计算机解决问题的方法和步骤。之所以强调计算机三个字,是因为计算机处理问题的方式和我们人类解决问题的方式有所不同。比如,在电视剧《宫》里看到一个智力题:把一头大象放进骄子里需要哪几个步骤?答案是:第一步,掀开轿帘;第二步,把大象放进去;第三步,放下轿帘。当然,可行性我们暂时不去考虑。然而,对于计算机来说,它没法去掀开轿帘,或者把大象放进轿子里。它只能通过计算机系统特有的指令去处理数据,以此来解决问题。

知道了什么是算法,那么如何去描述问题和算法呢?

以排序问题为例,描述一个问题可以用以下的方式:

输入:n个整数,存放于一个数组a之中。

输出:这n个数在数组a之中按从小到大的顺序存放。

给出了一个问题的输入和要求的输出,这个问题就确定了。描述一种算法,可以用伪代码或者是计算机语言。

对于排序问题,有多种算法。下面谈谈Insertion sort——插入法排序:

插入法排序的大概思想和摸扑克牌类似:当然,我们要求只有一个人摸牌,初始的数组就是放在桌上的一堆牌,而拿到手上的牌是排好序的。当我们摸起一张牌时,会按大小插入到已排好序的手牌之中。下面是基于linux系统,用C语言对改算法的实现:

#include <stdio.h>
#define MAX 10

int sort(int*);

int main()
{
    int input_array[MAX];
    int ret,i;

    printf("Please input the numbers :\n");
    for(i = 0; i < MAX; i++){
        scanf("%d", &input_array[i]);
    }

    printf("The numbers before being sorted :\n");
    for(i = 0; i < MAX; i++){
        printf("%d ", input_array[i]);
        }
    printf("\n");

    ret = sort(input_array);
    printf("The sorted numbers :\n");
    for(i = 0; i < MAX; i++){
        printf("%d ", input_array[i]);
        }
    printf("\n");

    return 0;
}

int sort(int* p)
{
    int i,j,temp;
    for(i = 1; i < MAX; i++){
        temp = p[i];
        for(j = i-1; j >= 0 && p[j] > temp; j--){
            p[j+1] = p[j];
        }
        p[j+1] = temp;
    }
}

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