顯示具有 about Linux 標籤的文章。 顯示所有文章
顯示具有 about Linux 標籤的文章。 顯示所有文章

2015年1月28日 星期三

Bash 實用技巧。


&  后台运行程序 
()   使用子shell, 比如 (cd ../../commlib/; make) 
$()  命令替换,和 ``的作用是一样的 
<(命令)  把命令的输出到一个临时文件 
<< HereDoc
使用举例:
比如你要在 shell 脚本中 使用 awk 脚本 
awk -f <(cat <<EOF
  /abc/{
   print $0;

EOF 
)
$(())  执行整数计算 $((66/2)) 
if (( 算术运算 )) 
if [[ 字符串运算 ]]

alias 定义命令别名
dot .  或 source 命令, 在当前shell中执行脚本
exec 可以重定向当前shell的文件描述符, 或运行另一个程序。

trap 可以捕获信号
nohup 防止ssh 挂起导致的问题 
screen 可以用来保持 会话,  不受ssh的关闭影响
export 导出变量给子shell使用
tee 可以 把 输出 分流
ENV_VAR=VALUE your_program 这样可以 为这一个程序 修改它环境变量,外部shell的环境变量没有被更改

tac 倒置文件

目录跳转

cd -   快速回到前一个路径
cd  回到用户的home目录
pushd, popd, dirs 实现多目录跳转
pushd 命令用来更改您的当前目录并将其存储在堆栈中。 popd 命令用来从堆栈的顶部移除目录并使您返回该位置。 dirs 命令来显示当前目录堆栈。(dir –v –p)
pushd +n; popd +n 可以操作虚拟目录堆栈

快速跳至常用目录

     你可能已经知道$PATH变量可以列出 bash的“搜索路径”——当在当前目录不能找到请求的文件时,bash会自动搜索的目录。不过,bash也支持$CDPATH变量,当试图改变目录时该变量列出cd命令转向的目录。为了使用这个特性,我们要对$CDPATH变量赋值一个目录列表,如下面的例子所示:
bash> CDPATH='.:~:/usr/local/apache/htdocs:/disk1/backups'
bash> export CDPATH
现在,无论何时使用cd命令,bash将会检查$CDPATH列表中的所有目录来查找要转向的目录名。

特殊参数

1) $*: 代表所有参数,其间隔为IFS内定参数的第一个字元 
2) $@: 与*星号类同。不同之处在於不参照IFS 
3) $#: 代表参数数量 
4) $?: 执行上一个指令的返回值 
5) $-: 最近执行的foreground pipeline的选项参数 
6) $$: 本身的Process ID 
7) $!: 执行上一个背景指令的PID 
8) $_: 显示出最後一个执行的命令

bash快捷键

Emacs风格
ctrl+p: 方向键 上 ↑ 
ctrl+n: 方向键下 ↓ 
ctrl+b: 方向键 ← 
alt+f: 光标右移一个单词 
ctrl+f :方向键 → 
alt+b: 光标左移一个单词 
ctrl+a:光标移到行首 
ctrl+e:光标移到行尾 
ctrl+k:清除光标后至行尾的内容。 
ctrl+d: 删除光标所在字母;注意和backspace以及ctrl+h的区别,这2个是删除光标前的字符 
ctrl+r:搜索之前打过的命令。会有一个提示,根据你输入的关键字进行搜索bash的history 
ctrl+m : 输入回车 
ctrl+i : 输入tab 
ctrl+[ : 输入esc
其它 ctrl+h:删除光标前一个字符,同 backspace 键相同。 
alt + p 非增量方式反向搜索历史 
alt + > 历史命令列表中的最后一行命令开始向前 
ctrl+u: 清除光标前至行首间的所有内容。 
ctrl+w: 移除光标前的一个单词 
ctrl+t: 交换光标位置前的两个字符 
ctrl+y: 粘贴或者恢复上次的删除 
ctrl+l:清屏,相当于clear。 
ctrl + xx 光标在行头与行尾进行跳转 
alt+r 撤销当前行的所有内容 
ctrl+z : 把当前进程转到后台运行 
ctrl+s : 锁住屏幕 
ctrl+q : 恢复屏幕 
ctrl+v key: 输入特殊字符 
alt + l 将当前光标处之后的字母转化成小写字母 
alt + u 将当前光标处之后的字母转化成大写字母 
ctrl + Alt + e 扩展命令行的内容(例如:ls  =>  ls  -l  --color=tty) 
ctrl+c:杀死当前进程, 输入模式下,中断输入的命令。 
ctrl+d:退出当前 Shell 
esc + . 快捷键可以轮询历史命令的参数或选项。 
esc + t 快捷键可以 置换前两个单词。 
输入重复字母 Esc {100} e 可以输入100个e字符
按多次{esc}可以补全 
{esc}{~}可以补全本机上的用户名 
{esc}{/}可以补全文件名 
{esc}{@}可以补全主机名,localhost可以方便地用 lo补全.

Bang Bang 历史命令

!!    重新执行上一条命令 
!N  重新执行第N条命令。比如 !3 
!-N 重新执行倒数第N条命令。!-3 
!string  重新执行以字符串打头的命令。 比如 !vim 
!?string?  重新执行包含字符串的命令。 比如 !?test.cpp? 
!?string?%  替换为: 最近包含这个字符串的命令的参数。比如:   vim !?test.cpp?% 
!$   替换为:上一条命令的最后一个参数。比如 vim !$ 
!!string  在上一条命令的后面追加 string ,并执行。 
!Nstring  在第N条指令后面追加string,并执行。 
^old^new^  对上一条指令进行替换 
修饰
:s/old/new/  对第N条指令中第一次出现的new替换为old。 比如 vim !?test.cpp?:s/cpp/c/ 
:gs/old/new/  全部替换 
:wn  w为数字, 取指令的第w个参数. 
:p 回显命令而不是执行, 比如 !ls:p  。 这个很有用, 你可以先查看你选的命令对不对,要执行时再使用!!

Bash相关文件

     /etc/profile 设置环境变量(所有用户) 
     ~/.bash_profile 设置环境变量(当前用户) 
     ~/.bashrc 
     ~/.bash_history 
     ~/.bash_logout

2015年1月16日 星期五

inet_ntoa 产生的段错误

inet_ntoa()在CENTOS532位版和64为机器上结果不一样。
 
32位正常打印,64位却是段错误,错在haddrp这个参数上,报错内容是说inet_ntoa的返回值是int,因此%s无法输出。
解决方法:
1. 如果用inet_ntoa()的话需要#include <arpa/inet.h> ,就正常了。
2. 使用inet_ntop()

2015年1月11日 星期日

Linux信号处理机制

在Linux中,信号是进程间通讯的一种方式,它采用的是异步机制。当信号发送到某个进程中时,操作系统会中断该进程的正常流程,并进入相应的信号处理函数执行操作,完成后再回到中断的地方继续执行。
需要说明的是,信号只是用于通知进程发生了某个事件,除了信号本身的信息之外,并不具备传递用户数据的功能。

1 信号的响应动作

每个信号都有自己的响应动作,当接收到信号时,进程会根据信号的响应动作执行相应的操作,信号的响应动作有以下几种:
  • 中止进程(Term)
  • 忽略信号(Ign)
  • 中止进程并保存内存信息(Core)
  • 停止进程(Stop)
  • 继续运行进程(Cont)
用户可以通过signalsigaction函数修改信号的响应动作(也就是常说的“注册信号”,在文章的后面会举例说明)。另外,在多线程中,各线程的信号响应动作都是相同的,不能对某个线程设置独立的响应动作。

2 信号类型

Linux支持的信号类型可以参考下面给出的列表。

2.1 在POSIX.1-1990标准中的信号列表

信号动作说明
SIGHUP1Term终端控制进程结束(终端连接断开)
SIGINT2Term用户发送INTR字符(Ctrl+C)触发
SIGQUIT3Core用户发送QUIT字符(Ctrl+/)触发
SIGILL4Core非法指令(程序错误、试图执行数据段、栈溢出等)
SIGABRT6Core调用abort函数触发
SIGFPE8Core算术运行错误(浮点运算错误、除数为零等)
SIGKILL9Term无条件结束程序(不能被捕获、阻塞或忽略)
SIGSEGV11Core无效内存引用(试图访问不属于自己的内存空间、对只读内存空间进行写操作)
SIGPIPE13Term消息管道损坏(FIFO/Socket通信时,管道未打开而进行写操作)
SIGALRM14Term时钟定时信号
SIGTERM15Term结束程序(可以被捕获、阻塞或忽略)
SIGUSR130,10,16Term用户保留
SIGUSR231,12,17Term用户保留
SIGCHLD20,17,18Ign子进程结束(由父进程接收)
SIGCONT19,18,25Cont继续执行已经停止的进程(不能被阻塞)
SIGSTOP17,19,23Stop停止进程(不能被捕获、阻塞或忽略)
SIGTSTP18,20,24Stop停止进程(可以被捕获、阻塞或忽略)
SIGTTIN21,21,26Stop后台程序从终端中读取数据时触发
SIGTTOU22,22,27Stop后台程序向终端中写数据时触发
:其中SIGKILLSIGSTOP信号不能被捕获、阻塞或忽略。

2.2 在SUSv2和POSIX.1-2001标准中的信号列表

信号动作说明
SIGTRAP5CoreTrap指令触发(如断点,在调试器中使用)
SIGBUS0,7,10Core非法地址(内存地址对齐错误)
SIGPOLLTermPollable event (Sys V). Synonym for SIGIO
SIGPROF27,27,29Term性能时钟信号(包含系统调用时间和进程占用CPU的时间)
SIGSYS12,31,12Core无效的系统调用(SVr4)
SIGURG16,23,21Ign有紧急数据到达Socket(4.2BSD)
SIGVTALRM26,26,28Term虚拟时钟信号(进程占用CPU的时间)(4.2BSD)
SIGXCPU24,24,30Core超过CPU时间资源限制(4.2BSD)
SIGXFSZ25,25,31Core超过文件大小资源限制(4.2BSD)
:在Linux 2.2版本之前,SIGSYSSIGXCPUSIGXFSZ以及SIGBUS的默认响应动作为Term,Linux 2.4版本之后这三个信号的默认响应动作改为Core。

2.3 其它信号

信号动作说明
SIGIOT6CoreIOT捕获信号(同SIGABRT信号)
SIGEMT7,-,7Term实时硬件发生错误
SIGSTKFLT-,16,-Term协同处理器栈错误(未使用)
SIGIO23,29,22Term文件描述符准备就绪(可以开始进行输入/输出操作)(4.2BSD)
SIGCLD-,-,18Ign子进程结束(由父进程接收)(同SIGCHLD信号)
SIGPWR29,30,19Term电源错误(System V)
SIGINFO29,-,-电源错误(同SIGPWR信号)
SIGLOST-,-,-Term文件锁丢失(未使用)
SIGWINCH28,28,20Ign窗口大小改变时触发(4.3BSD, Sun)
SIGUNUSED-,31,-Core无效的系统调用(同SIGSYS信号)
注意:列表中有的信号有三个值,这是因为部分信号的值和CPU架构有关,这些信号的值在不同架构的CPU中是不同的,三个值的排列顺序为:1,Alpha/Sparc;2,x86/ARM/Others;3,MIPS。
例如SIGSTOP这个信号,它有三种可能的值,分别是17、19、23,其中第一个值(17)是用在Alpha和Sparc架构中,第二个值(19)用在x86、ARM等其它架构中,第三个值(23)则是用在MIPS架构中的。

3 信号机制

文章的前面提到过,信号是异步的,这就涉及信号何时接收、何时处理的问题。
我们知道,函数运行在用户态,当遇到系统调用、中断或是异常的情况时,程序会进入内核态。信号涉及到了这两种状态之间的转换,过程可以先看一下下面的示意图:
信号处理机制示意图
接下来围绕示意图,将信号分成接收、检测和处理三个部分,逐一讲解每一步的处理流程。

3.1 信号的接收

接收信号的任务是由内核代理的,当内核接收到信号后,会将其放到对应进程的信号队列中,同时向进程发送一个中断,使其陷入内核态。
注意,此时信号还只是在队列中,对进程来说暂时是不知道有信号到来的。

3.2 信号的检测

进程陷入内核态后,有两种场景会对信号进行检测:
  • 进程从内核态返回到用户态前进行信号检测
  • 进程在内核态中,从睡眠状态被唤醒的时候进行信号检测
当发现有新信号时,便会进入下一步,信号的处理。

3.3 信号的处理

信号处理函数是运行在用户态的,调用处理函数前,内核会将当前内核栈的内容备份拷贝到用户栈上,并且修改指令寄存器(eip)将其指向信号处理函数。
接下来进程返回到用户态中,执行相应的信号处理函数。
信号处理函数执行完成后,还需要返回内核态,检查是否还有其它信号未处理。如果所有信号都处理完成,就会将内核栈恢复(从用户栈的备份拷贝回来),同时恢复指令寄存器(eip)将其指向中断前的运行位置,最后回到用户态继续执行进程。
至此,一个完整的信号处理流程便结束了,如果同时有多个信号到达,上面的处理流程会在第2步和第3步骤间重复进行。

2015年1月9日 星期五

Linux下DIR,dirent,stat等结构体详解

  
  DIR结构体类似于FILE,是一个内部结构,以下几个函数用这个内部结构保存当前正在被读取的目录的有关信息(摘自《UNIX环境高级编程(第二版)》)。函数 DIR *opendir(const char *pathname),即打开文件目录,返回的就是指向DIR结构体的指针,而该指针由以下几个函数使用:

首先说说DIR这一结构体,以下为DIR结构体的定义:
  1. struct __dirstream
  2.    {
  3.     void *__fd;
  4.     char *__data;
  5.     int __entry_data;
  6.     char *__ptr;
  7.     int __entry_ptr;
  8.     size_t __allocation;
  9.     size_t __size;
  10.     __libc_lock_define (, __lock)
  11.    };
  12. typedef struct __dirstream DIR;

DIR结构体类似于FILE,是一个内部结构,以下几个函数用这个内部结构保存当前正在被读取的目录的有关信息(摘自《UNIX环境高级编程(第二版)》)。函数 DIR *opendir(const char *pathname),即打开文件目录,返回的就是指向DIR结构体的指针,而该指针由以下几个函数使用:

  1. struct dirent *readdir(DIR *dp);
  2. void rewinddir(DIR *dp);
  3. int closedir(DIR *dp);
  4. long telldir(DIR *dp);
  5. void seekdir(DIR *dp,long loc);

关于DIR结构,我们知道这么多就可以了,没必要去再去研究他的结构成员。
接着是dirent结构体,首先我们要弄清楚目录文件(directory file)的概念:这种文件包含了其他文件的名字以及指向与这些文件有关的信息的指针(摘自《UNIX环境高级编程(第二版)》)。从定义能够看出,dirent不仅仅指向目录,还指向目录中的具体文件,readdir函数同样也读取目录下的文件,这就是证据。以下为dirent结构体的定义:

  1. struct dirent
  2. {
  3.   long d_ino; /* inode number 索引节点号 */
  4.     off_t d_off; /* offset to this dirent 在目录文件中的偏移 */
  5.     unsigned short d_reclen; /* length of this d_name 文件名长 */
  6.     unsigned char d_type; /* the type of d_name 文件类型 */
  7.     char d_name [NAME_MAX+1]; /* file name (null-terminated) 文件名,最长255字符 */
  8. }

从上述定义也能够看出来,dirent结构体存储的关于文件的信息很少,所以dirent同样也是起着一个索引的作用,如果想获得类似ls -l那种效果的文件信息,必须要靠stat函数了。
通过readdir函数读取到的文件名存储在结构体dirent的d_name成员中,而函数
int stat(const char *file_name, struct stat *buf);
的作用就是获取文件名为d_name的文件的详细信息,存储在stat结构体中。以下为stat结构体的定义:

  1. struct stat {
  2.         mode_t     st_mode;       //文件访问权限
  3.         ino_t      st_ino;       //索引节点号
  4.         dev_t      st_dev;        //文件使用的设备号
  5.         dev_t      st_rdev;       //设备文件的设备号
  6.         nlink_t    st_nlink;      //文件的硬连接数
  7.         uid_t      st_uid;        //所有者用户识别号
  8.         gid_t      st_gid;        //组识别号
  9.         off_t      st_size;       //以字节为单位的文件容量
  10.         time_t     st_atime;      //最后一次访问该文件的时间
  11.         time_t     st_mtime;      //最后一次修改该文件的时间
  12.         time_t     st_ctime;      //最后一次改变该文件状态的时间
  13.         blksize_t st_blksize;    //包含该文件的磁盘块的大小
  14.         blkcnt_t   st_blocks;     //该文件所占的磁盘块
  15.       };

这个记录的信息就很详细了吧,呵呵。
最后,总结一下,想要获取某目录下(比如a目下)b文件的详细信息,我们应该怎样做?
首先,我们使用opendir函数打开目录a,返回指向目录a的DIR结构体c。
接着,我们调用readdir(c)函数读取目录a下所有文件(包括目录),返回指向目录a下所有文件的dirent结构体d。
然后,我们遍历d,调用stat(d->name,stat *e)来获取每个文件的详细信息,存储在stat结构体e中。
总体就是这样一种逐步细化的过程,在这一过程中,三种结构体扮演着不同的角色。

以下範例為開啟某目錄後,搜尋目錄下所有".c"副檔名,此處是用指針偏移的方式來取得附檔名,另外也可以用函數fnmatch來實現。

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<dirent.h>
#include<stdlib.h>

int main (){
    DIR *d;
    struct dirent *de;
    char * buf;

    d=opendir("/root/pratice/day5");
    if(d==NULL){printf("%m\n"),exit(-1);}

    while((de=readdir(d)))
    {
    int length=strlen(de->d_name);
    int ext=length-2;
    buf=strstr(de->d_name+ext,".c");
    if (buf){printf("%s\n",de->d_name);}
    }
    return 0;
}

2015年1月6日 星期二

curses.h No such file or directory problem

Fix this error on RHEL / Fedora / CentOS Linux

Type the following yum command at a shell prompt as root user:
# yum install ncurses-devel ncurses
Sample output:
Loading "downloadonly" plugin
Loading "rhnplugin" plugin
Loading "security" plugin
rhel-x86_64-server-vt-5   100% |=========================| 1.2 kB    00:00
rhel-x86_64-server-5      100% |=========================| 1.2 kB    00:00
Setting up Install Process
Parsing package install arguments
Package ncurses - 5.5-24.20060715.x86_64 is already installed.
Package ncurses - 5.5-24.20060715.i386 is already installed.
Resolving Dependencies
--> Running transaction check
---> Package ncurses-devel.x86_64 0:5.5-24.20060715 set to be updated
---> Package ncurses-devel.i386 0:5.5-24.20060715 set to be updated
--> Finished Dependency Resolution
Dependencies Resolved
=============================================================================
 Package                 Arch       Version          Repository        Size
=============================================================================
Installing:
 ncurses-devel           x86_64     5.5-24.20060715  rhel-x86_64-server-5  1.7 M
 ncurses-devel           i386       5.5-24.20060715  rhel-x86_64-server-5  1.6 M
Transaction Summary
=============================================================================
Install      2 Package(s)
Update       0 Package(s)
Remove       0 Package(s)
Total download size: 3.3 M
Is this ok [y/N]: y
Downloading Packages:
(1/2): ncurses-devel-5.5- 100% |=========================| 1.6 MB    00:01
(2/2): ncurses-devel-5.5- 100% |=========================| 1.7 MB    00:01
Running rpm_check_debug
Running Transaction Test
Finished Transaction Test
Transaction Test Succeeded
Running Transaction
  Installing: ncurses-devel                ######################### [1/2]
  Installing: ncurses-devel                ######################### [2/2]
Installed: ncurses-devel.x86_64 0:5.5-24.20060715 ncurses-devel.i386 0:5.5-24.20060715
Complete!

Fix this error on Debian / Ubuntu Linux

Open terminal and type the following apt-get command to install ncurses:
$ sudo apt-get install libncurses5-dev libncursesw5-dev
Now you should able to compile any program under Linux using ncurses.

2015年1月2日 星期五

在Linux下如何使用GCC编译程序、简单生成 静态库及动态库。

本文适用于Linux下开发初学者。本文初步讲解在Linux下如何使用GCC编译程序、简单生成静态库及动态库。

一、关于安装。一般系统默认是安装好编译器的,并且网络上有大量资料介绍不同发行版本下的安装问题,本文不再描述。

二、C编程中的文件后缀名介绍
    .a 静态库(打包文件)
    .c 未经过预处理的C源码
    .h C头文件   
    .i 经过预处理的C源码
    .o 编译之后产生的目标文件
    .s 生成的汇编语言代码
    .so 动态库(动态链接库)
    解释:*.a是我们在编译过后用ar打包生成的静态库;*.c一般使我们自己编辑的代码,使我们劳动的结晶;*.h一般是 我们手工生成的接口文件,如果愿意,也可在*.c完成后用GCC的选项-aux-info帮我们生成;*.i是经过预处理后的源码,是由GCC在选项-E编译下自动生成 的文件;*.o是编 译后产生的目标文件;*.s是GCC在选项-S编译下生成的汇编语言代码,对于性能要求很高的程序可以先生成汇编语言文件并对汇编做优化,然后用优 化后的汇编生成目标文件并链接;*.so是动态库,通过GCC的-fpic -shared选项生成。

三、hello.c的编译过程

    本小节的演示都针对文件 hello.c 进行
  1. /*
  2.  * hello.c
  3.  */

  4.   #include <stdio.h>
  5.   int  main()
  6.   {
  7.     printf("hello, world!/n");
  8.     return 0;
  9.   }
1. 直接生成可执行程序
  1. $ gcc -o hello hello.c
  2. $ ./hello
  3. hello, world!

  4. 如 下编译方式 结果相同:
  5. $ gcc hello.c -o hello
  6. $ ./hello
  7. hello, world!

  8. 如 下编译方式 有别于以上编译方 案(具体查找ELF和a.out文件格式差别的网络资料,对于此处结果是无任何区别的):
  9. $ gcc hello.c 
  10. $ ./a.out 
  11. hello, world!

2. 生成预处理后的文件 hello.i
  1. $ gcc -E hello.c -o hello.i
  2. $ ls
  3. a.out  hello  hello.c  hello.i
  4. hello.i 就 是新生成的文件

  5. 如下语句结果相同:
  6. $ gcc -E -o hello.i hello.c 

  7. 如 果不设定输出文件,则打印到标准终端,此时我们可以用 less 查看:
  8. $ gcc -E hello.c | less
  9. # 1 "hello.c"
  10. # 1 "<built-in>"
  11. # 1 "<command line>"
  12. # 1 "hello.c"
  13. # 1 "/usr/include/stdio.h" 1 3 4
  14. # 28 "/usr/include/stdio.h" 3 4
  15. # 1 "/usr/include/features.h" 1 3 4
  16. # 329 "/usr/include/features.h" 3 4
  17. ..............................

  18. 或 者执行:
  19. $ gcc -E hello.c -o hello.i
  20. $ vi hello.i
  21.   1 # 1 "hello.c"
  22.   2 # 1 "<built-in>"
  23.   3 # 1 "<command line>"
  24.   4 # 1 "hello.c"
  25.   5 # 1 "/usr/include/stdio.h" 1 3 4
  26.   6 # 28 "/usr/include/stdio.h" 3 4
  27.   7 # 1 "/usr/include/features.h" 1 3 4
  28.   8 # 329 "/usr/include/features.h" 3 4

  29. .......... < 中间部分略> ..................

  30. 929 # 844 "/usr/include/stdio.h" 3 4
  31. 930 
  32. 931 # 2 "hello.c" 2
  33. 932 
  34. 933 int main()
  35. 934 {
  36. 935         printf("hello, world!/n");
  37. 936 
  38. 937         return 0;
  39. 938 }

  40. 可 见,将近1000行的代码,我们的只占了最末8行。

3.生成汇编语言文件 hello.s
  1. $ gcc -S hello.c -o hello.s
  2. $ ls
  3. a.out  hello  hello.c  hello.i  hello.s
  4. hello.s 就是新生成的文件

  5. 如下语句结果相同:
  6. $ gcc -S -o hello.s hello.c

  7. 如 下语句结果相同:
  8. $ gcc -S hello.c

  9. 也 可以采用前一步骤产生的中间文件生成汇编文件:
  10. $ gcc -S hello.i -o hello.s
  11. $ gcc -S -o hello.s hello.i
  12. $ gcc -S hello.i


  13. 生 成的汇编部分代码如下:
  14. $ vi hello.s 
  15.   1         .file   "hello.c"
  16.   2         .section        .rodata
  17.   3 .LC0:
  18.   4         .string "hello, world!"
  19.   5         .text
  20.   6 .globl main
  21.   7         .type   main, @function
  22.   8 main:
  23.   9         leal    4(%esp), %ecx
  24.  10         andl    $-16, %esp
  25.  11         pushl   -4(%ecx)
  26.  12         pushl   %ebp
  27. // 注释:如果你熟悉,就可以对部分汇编优化以达到更好效果。
4.生成目标文件 hello.o
  1. $ gcc -c hello.c -o hello.o
  2. $ ls
  3. a.out  hello  hello.c  hello.i  hello.o  hello.s
  4. hello.o 就是新生成的目标文件:

  5. 如下语句结果相同:
  6. $ gcc -c -o hello.o hello.c 

  7. 如 下语句结果相同:
  8. $ gcc -c hello.c

  9. 也 可以采用前面步骤产生的中间文件hello.i或hello.s来生成目标文件:
  10. $ gcc -c hello.i
  11. $ gcc -c hello.s

  12. 我 们可以用 objdump 查看 hello.o 的二进制码:
  13. $ objdump -s hello.o

  14. hello.o:     file format elf32-i386

  15. Contents of section .text:
  16.  0000 8d4c2404 83e4f0ff 71fc5589 e55183ec  .L$.....q.U..Q..
  17.  0010 04c70424 00000000 e8fcffff ffb80000  ...$............
  18.  0020 000083c4 04595d8d 61fcc3             .....Y].a..     
  19. Contents of section .rodata:
  20.  0000 68656c6c 6f2c2077 6f726c64 2100      hello, world!.  
  21. Contents of section .comment:
  22.  0000 00474343 3a202847 4e552920 342e312e  .GCC: (GNU) 4.1.
  23.  0010 31203230 30373031 30352028 52656420  1 20070105 (Red 
  24.  0020 48617420 342e312e 312d3532 2900      Hat 4.1.1-52).  
5. 采用中间级文件生成可执行程序
  1. $ gcc -o hello hello.i
  2. $ ./hello
  3. hello, world!

  4. $ gcc -o hello hello.s
  5. $ ./hello
  6. hello, world!

  7. $ gcc -o hello hello.o
  8. $ ./hello
  9. hello, world!
四、 静态库的生成
    linux下静态库的生成比较方便。在生成目标文件后用 ar 打包即可。在中大型项目中一个模块一般会做成一个静态库,以方便管理、提高编译、链接效率。
    本小节的展示针对 main.c、func1.c、func2.c三个文件
  1. /*
  2.  * main.c
  3.  */
  4. #include <stdio.h>

  5. extern int func1();
  6. extern int func2();

  7. int main()
  8. {
  9.         int i;

  10.         i = func1();
  11.         printf("func1 return = %d/n",i);

  12.         i = func2();
  13.         printf("func2 return = %d/n",i);

  14.         return 0;
  15. }

-----------------------------------------------------
  1. /*
  2.  * func1.c
  3.  */
  4. int func1()
  5. {
  6.         return 100;
  7. }
-----------------------------------------------------
  1. /*
  2.  * func2.c
  3.  */
  4. int func2()
  5. {
  6.         return 200;
  7. }
一 下是编译指 令:
  1. $ gcc -c func1.c
  2. $ gcc -c func2.c
  3. $ ls
  4. func1.c  func1.o  func2.c  func2.o  main.c

  5. func1.o 和 func2.o 是 我们生成的目标文件。打包指令如下:
  6. $ ar -r libfunc.a func1.o func2.o

  7. 我 们查看 libfunc.a 中的文件:
  8. $ ar -t libfunc.a 
  9. func1.o
  10. func2.o

  11. 现 在用静态库和 main.c 共同生成目标程序:
  12. $ gcc -o main main.c libfunc.a
  13. $ ./main 
  14. func1 return = 100
  15. func2 return = 200

  16. 和 我们的预期相符合。下面我们进入动态库。


五、动态库的生成
    linux下动态库的生成通过GCC选项实现。案例程序和静态库中的相同。一下是操作指令:
  1. 首 先我们生成目标文件,但是需要加编译器选项 -fpic 和链接器选项 -shared
  2. $ gcc -fpic -c func1.c
  3. $ gcc -fpic -c func2.c
  4. $ gcc -shared -o libfunc.so func1.o func2.o
  5. $ ls
  6. func1.c  func1.o  func2.c  func2.o  libfunc.so  main.c

  7. libfunc.so 就是我们生成的目标动态库。我们用动态库和 main.c 生成目标程序:
  8. $ gcc -o main main.c -L. -lfunc

  9. 注 意,我们用 -L. -lfunc 作为编译选项。-L. 表从当前目录查找需要的动态库,-lfunc 是动态库的调用规则。Linux系统下的动态库命名方 式是 lib*.so,而在链接时表示位 -l* , *是自己起的库名。下面我们运行它:

  10. $ ./main 
  11. ./main: error while loading shared libraries: libfunc.so: cannot open shared object file: No such file or directory

  12. 提 示一个错误, 指示无法找到动态库。在linux下最方便的解决方案是拷贝libfunc.so到绝对目录 /lib 下。但是只有超级用户才有这个权限。另外一个方案 是更改环境变量 LD_LIBRARY_PATH。如下:
  13. $ $ export LD_LIBRARY_PATH=`pwd`
  14. $ ./main 
  15. func1 return = 100
  16. func2 return = 200

  17. 运 行成功。现在我们更改动态库的函数而不重新链接。如下:
  18. 更改 func1.c 为:
  19. int func1()
  20. {
  21.         return 101;
  22. }
  23. 更 改 func2.c 为:
  24. int func2()
  25. {
  26.         return 202;
  27. }
  28. 重 新生成库:
  29. $ gcc -fpic -shared func1.c func2.c -o libfunc.so
  30. $ ./main 
  31. func1 return = 101
  32. func2 return = 202

  33. 可 以看出,动态库已经更新了。
六、结束语
    本文简单介绍了linux下如何使用gcc进行编译程序、以及简 单的静态、动态库的生成。静态库提供了一种打包管理方案,而动态库使程序局部更新成为了可能,更重要的是,当有多份实例存在时,动态库可减小内存的消耗 (只占用一份代码空间)。