unbutun的专栏 此人博客需要关注

unbutun的专栏	
ftrace和perf
 
今天没去参加吃喝玩乐,跑去加班,不过心情还是愉悦的,因为有收获。真是利器啊。

调试例子ftrace_demo.c

/*
* ftrace_demo.c
*/
#include
 
#include 
 
#include 
 
MODULE_LICENSE("GPL");
 
static int ftrace_demo_init(void)
{
        trace_printk("Can not see this in trace unless loaded for the second time\n");
        tracing_off();
        return 0;
}
 
static void ftrace_demo_exit(void)
{
        trace_printk("Module unloading\n");
        tracing_off();
}
 
module_init(ftrace_demo_init);
module_exit(ftrace_demo_exit);
Makefile

obj-m += ftrace_demo.o
 
all:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
ubuntu 10.10的内核里已经打开ftrace,看看/sys/kernel/debug/tracing/ 下的README,这就是使用方法。

tracing mini-HOWTO:
 
# mount -t debugfs nodev /sys/kernel/debug
 
# cat /sys/kernel/debug/tracing/available_tracers
wakeup preemptirqsoff preemptoff irqsoff function sched_switch nop
 
# cat /sys/kernel/debug/tracing/current_tracer
nop
# echo sched_switch > /sys/kernel/debug/tracing/current_tracer
# cat /sys/kernel/debug/tracing/current_tracer
sched_switch
# cat /sys/kernel/debug/tracing/trace_options
noprint-parent nosym-offset nosym-addr noverbose
# echo print-parent > /sys/kernel/debug/tracing/trace_options
# echo 1 > /sys/kernel/debug/tracing/tracing_enabled
# cat /sys/kernel/debug/tracing/trace > /tmp/trace.txt
# echo 0 > /sys/kernel/debug/tracing/tracing_enabled
巧妙的应用了gcc 的-pg选项,重载mcount,实现tracing的功能。据yakui说,这个只适用于kernel space,于是有了[4],于是调试user space也方便了。

之后是如何在要跟踪的代码中插入断点,参考[5]中的tracepoints.txt. 有几个commit可以参考。

kernel 中的 99ee7fac189893c90145a22b86bbcfdc98f69a9c 和 9d34e5db07303c9609053e2e651aa6d1fc74e923。 第二个,还没看明白。

折腾的功夫没到家啊。

继续前面的学习,并补充。

编译kernel,使之支持ftrace,需要打开如下选项

CONFIG_FUNCTION_TRACER
CONFIG_FUNCTION_GRAPH_TRACER
CONFIG_STACK_TRACER
CONFIG_DYNAMIC_FTRACE
[7]中的代码,分析如下

TRACE_EVENT(sched_switch,
        TP_PROTO(struct rq *rq, struct task_struct *prev,
                 struct task_struct *next),
 
        TP_ARGS(rq, prev, next),
 
        TP_STRUCT__entry(
                __array(        char,   prev_comm,      TASK_COMM_LEN   )
                __field(        pid_t,  prev_pid                        )
                __field(        int,    prev_prio                       )
                __field(        long,   prev_state                      )
                __array(        char,   next_comm,      TASK_COMM_LEN   )
                __field(        pid_t,  next_pid                        )
                __field(        int,    next_prio                       )
        ),
 
        TP_fast_assign(
                memcpy(__entry->next_comm, next->comm, TASK_COMM_LEN);
                __entry->prev_pid       = prev->pid;
                __entry->prev_prio      = prev->prio;
                __entry->prev_state     = prev->state;
                memcpy(__entry->prev_comm, prev->comm, TASK_COMM_LEN);
                __entry->next_pid       = next->pid;
                __entry->next_prio      = next->prio;
        ),
 
        TP_printk("prev_comm=%s prev_pid=%d prev_prio=%d prev_state=%s ==> next_comm=%s next_pid=%d next_prio=%d",
                __entry->prev_comm, __entry->prev_pid, __entry->prev_prio,
                __entry->prev_state ?
                __print_flags(__entry->prev_state, "|",
                        { 1, "S"} , { 2, "D" }, { 4, "T" }, { 8, "t" },
                        { 16, "Z" }, { 32, "X" }, { 64, "x" },
                        { 128, "W" }) : "R",
                __entry->next_comm, __entry->next_pid, __entry->next_prio)
);
#define TRACE_EVENT(name, proto, args, tstruct, assign, print) \
        DECLARE_EVENT_CLASS(name,                              \
                             PARAMS(proto),                    \
                             PARAMS(args),                     \
                             PARAMS(tstruct),                  \
                             PARAMS(assign),                   \
                             PARAMS(print));                   \
        DEFINE_EVENT(name, name, PARAMS(proto), PARAMS(args));
kernel里的宏,翻起来太累了,看了include/trace/ftrace.h,彻底晕菜。还是让gcc来帮忙吧。radeon驱动里的例子,非常适合借鉴

  gcc -E radeon_trace_points.c  -I/work/linux-2.6/include -I/work/linux-2.6/include/drm -I/work/linux-2.6/arch/x86/include -I. -DCONFIG_TRACEPOINTS
只看

TRACE_EVENT(radeon_bo_create,
            TP_PROTO(struct radeon_bo *bo),
            TP_ARGS(bo),
            TP_STRUCT__entry(
                             __field(struct radeon_bo *, bo)
                             __field(u32, pages)
                             ),
 
            TP_fast_assign(
                           __entry->bo = bo;
                           __entry->pages = bo->tbo.num_pages;
                           ),
            TP_printk("bo=%p, pages=%u", __entry->bo, __entry->pages)
);
static const char __tpstrtab_radeon_bo_create[] __attribute__((section("__tracepoints_strings"))) = "radeon_bo_create";
struct tracepoint __tracepoint_radeon_bo_create __attribute__((section("__tracepoints"))) = {
        __tpstrtab_radeon_bo_create,
        0,
        ((void *)0),
        ((void *)0),
        ((void *)0) };
static struct tracepoint * const __tracepoint_ptr_radeon_bo_create __used __attribute__((section("__tracepoints_ptrs"))) = __tracepoint_radeon_bo_create;;;

1. ftrace简介

http://www.ibm.com/developerworks/cn/linux/l-cn-ftrace/index.html

2. 使用 ftrace 调试 Linux 内核

http://linux.cn/home/space-2-do-blog-id-326.html

http://linux.cn/home/space-2-do-blog-id-329.html

http://linux.cn/home/space-2-do-blog-id-331.html

3. Perf — Linux下的系统性能调优工具介绍http://blog.csdn.net/bluebeach/archive/2010/09/28/5912062.aspx

4. trace: Add user-space event tracing/injection

http://marc.info/?l=linux-kernel&m=128999569714133&w=2

5.第一手资料在kernel目录中 Documentation/trace/*

6. TRACE_EVENT()

http://lwn.net/Articles/379903/

7. sched_switch TRACE_EVENT definition

http://lwn.net/Articles/379903/

http://lwn.net/Articles/381064/

http://lwn.net/Articles/383362/

作者:unbutun 发表于2011-7-11 20:44:21 原文链接
阅读:3 评论:0 查看评论
console,uart,tty的关联关系
 
/dev/console是系统控制台,或者说是物理控制台,而/dev/ttyn则是虚拟控制台,你可以传导诸如console=ttyS1的启动参数给内核,使得ttyS1(第二个串口控制台)成为系统控制台。如果没有传导这个参数给内核,内核就会自动寻找可用的设备作为系统控制台,首先是VGA显示卡,其次才是串口。

/dev/console的初始早于虚拟控制台。启动信息都是打印到/dev/console。

single模式登录,也是登录到/dev/console

内核的printk信息,比如netfilter过滤了包的信息,也会打印到/dev/console,所以有人会问为何屏幕都是包过滤的信息。
/proc/sys/kernel/printk,控制了printk的level。
/dev/tty是当前进程连接的控制台,你可以echo something >/dev/tty 看看结果。
/dev/tty0则是当前前台的视频控制台,你也可以echo something看看。
/dev/tty1-n则是虚拟控制台。

晕。说错纠正啊。

 

 

tty是一类char设备的通称,它们有相同的特性,比如对^C的处理,驱动使用tty_register_driver注册一个tty。

/dev/console是一个虚拟的tty,它映射到真正的tty上,console有多种含义,这里特指printk输出的设备,驱动使用register_console注册一个console。

console和tty有很大区别:console是个只输出的设备,功能很简单,只能在内核中访问;tty是char设备,可以被用户程序访问。

实际的驱动比如串口对一个物理设备会注册两次,一个是tty,一个是console,并通过在console的结构中记录tty的主次设备号建立了联系。

在内核中,tty和console都可以注册多个。当内核命令行上指定console=ttyS0之类的参数时,首先确定了printk实际使用那个 console作为输出,其次由于console和tty之间的对应关系,打开/dev/console时,就会映射到相应的tty上。用一句话说: /dev/console将映射到默认console对应的tty上。

 

作者:unbutun 发表于2011-7-10 18:59:31 原文链接
阅读:3 评论:0 查看评论
郁闷的串口中断 nobody cared


 

 

这几天碰到一个很奇怪的问题, kernel 启动的时候,会碰到 抱怨 irq 19: nobody cared. 这个中断是串口中断. 而且我确认串口是可以work的. 因为在 early console->console 已经成功了. 

经过一番搜索, 发现把kernel_init 函数中的 

/* Open the /dev/console on the rootfs, this should never fail */ 
if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0) 
printk(KERN_WARNING "Warning: unable to open an initial console.\n"); 
注释掉就可以ok. 看来问题是 在 sys_open((const char __user *) "/dev/console" 中. 

我的问题是: 到这里的时候, rootfs 还没有被 mount上(我的rootfs是放在USB上面的),我加了一些log,发现在 打开/dev/console后才会 去mount USB的rootfs. 那么这个时候去open /dev/console, 一定会失败了. 但是 注释说 , this should never fail. 是不是在mount rootfs之前,kernel 会创建这个设备? 在那里创建的? 


NET: Registered protocol family 17 
NET: Registered protocol family 15 
Registering platform devices 
irq 19: nobody cared (try booting with the "irqpoll" option) 
Call Trace: 
[] dump_stack+0x8/0x34 
[] __report_bad_irq+0x44/0xe8 
[] note_interrupt+0x188/0x250 
[] handle_percpu_irq+0xb4/0xe0 
[] do_IRQ+0x2c/0x40 
[] ret_from_irq+0x0/0x4 
[] __do_softirq+0x98/0x270 
[] do_softirq+0xa0/0xc8 
[] irq_exit+0xb4/0xd8 
[] ret_from_irq+0x0/0x4 
[] __setup_irq+0x1f8/0x440 
[] request_threaded_irq+0x124/0x188 
[] serial8250_startup+0xb6c/0xca8 
[] uart_startup+0x74/0x290 
[] uart_open+0x190/0x648 
[] tty_open+0x288/0x850 
[] chrdev_open+0x1e4/0x370 
[] __dentry_open+0xf0/0x348 
[] do_last+0x720/0xa10 
[] do_filp_open+0x1f0/0x670 
[] do_sys_open+0x9c/0x1d8 
[] kernel_init+0x130/0x1b4 
[] kernel_thread_helper+0x10/0x18 

handlers: 
[] (serial8250_interrupt+0x0/0x188) 
Disabling IRQ #19 
irq 19: nobody cared (try booting with the "irqpoll" option) 
Call Trace: 
[] dump_stack+0x8/0x34 
[] __report_bad_irq+0x44/0xe8 
[] note_interrupt+0x188/0x250 
[] handle_percpu_irq+0xb4/0xe0 
[] do_IRQ+0x2c/0x40 
[] ret_from_irq+0x0/0x4 
[] __do_softirq+0x98/0x270 
[] do_softirq+0xa0/0xc8 
[] irq_exit+0xb4/0xd8 
[] ret_from_irq+0x0/0x4 
[] __setup_irq+0x1f8/0x440 
[] request_threaded_irq+0x124/0x188 
[] serial8250_startup+0xb6c/0xca8 
[] uart_startup+0x74/0x290 
[] uart_open+0x190/0x648 
[] tty_open+0x288/0x850 
[] chrdev_open+0x1e4/0x370 
[] __dentry_open+0xf0/0x348 
[] do_last+0x720/0xa10 
[] do_filp_open+0x1f0/0x670 
[] do_sys_open+0x9c/0x1d8 
[] kernel_init+0x130/0x1b4 
[] kernel_thread_helper+0x10/0x18 


 

grep了一把, 发现是在 drivers/char/tty_io.c 中 创建 /dev/console. 

那为什么会open的时候有问题呢? 继续debug 

3128 static int __init tty_init(void) 
3129 { 
3130 cdev_init(&tty_cdev, &tty_fops); 
3131 if (cdev_add(&tty_cdev, MKDEV(TTYAUX_MAJOR, 0), 1) || 
3132 register_chrdev_region(MKDEV(TTYAUX_MAJOR, 0), 1, "/dev/tty") < 0) 
3133 panic("Couldn't register /dev/tty driver\n"); 
3134 device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 0), NULL, 
3135 "tty"); 
3136 
3137 cdev_init(&console_cdev, &console_fops); 
3138 if (cdev_add(&console_cdev, MKDEV(TTYAUX_MAJOR, 1), 1) || 
3139 register_chrdev_region(MKDEV(TTYAUX_MAJOR, 1), 1, "/dev/console") < 0) 
3140 panic("Couldn't register /dev/console driver\n"); 
3141 device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 1), NULL, 
3142 "console"); 


http://lists.openwall.net/linux-kernel/2010/03/03/197 

这段代码看来是 3/3 kernel中刚改过的. 原来 打开 /dev/console 是在 init_post 中做的. 

感觉你遇到的问题,跟我的类似阿。 
我也不知道,什么时候,谁在/dev下创建的设备。 

其实谁创建的,我知道,是udev线程创建的。关键谁让udev创建的? 
LVM,块设备,设计到很多东西。 

这个问题 今天解决了。 

现在看一下表面现象: 表面现象是 irq 19没有人 care。 那么我们的第一反应是:irq 19的中断处理函数没有注册。 
但是从kernel给出的信息又说明它找到了handlers: 
[] (serial8250_interrupt+0x0/0x188) 
因此,没有注册irq 19的中断处理程序的推理不成立。 

那么继续想一下,kernel是如何判断这个irq没有人care呢? 具体的代码在note_interrupt 里面。kernel 会记录某一个irq没有被处理的次数。如果在某一段时间里面这个irq没有被处理达到一个大的阀值,kernel 就认为这个irq没有人care。 因此,实际上,这个错误信息是: irq 19 在很长的时间里面没有人处理。 

那再回到具体的平台中。平台的IRQ 19是UART,这个中断是连接到MIPS CPU的IP3。熟悉MIPS的同学都知道,中断由 MIPS CPU的中断处理程序再根据具体的中断引脚 调用具体的doIRQ。 比如,在MIPS 中断处理中,如果发现IP3被 set,就会调用 IRQ(19)。当然不同的平台中断的路由不一样,但是大体的思路是一样的。 

跟踪代码,发现代码进入了MIPS的中断处理函数,也确实判断了IP3被set,然后也调用了 doIRQ(19)。但是,为什么kernel 认为这个中断没有人处理呢? 实际上,代码也进了 serial8250_interrupt。 这个函数会读 串口的IIR寄存器,这个寄存器反应了串口的中断状态,读出来的值是 0x1,也就是 NO INTERRUPT PENDING. 因此,会认为当前没有串口发生,跳出中断处理函数。 从而导致kernel 认为该中断没有处理。 

那么就有个问题。 刚才说 串口中断是接到CPU 的IP3上面。先是cpu认为IP3被set,然后才调用串口中断处理程序。 如果没有串口中断,为什么CPU的IP3会被set,从而MIPS CPU认为当前有中断发生呢?这个是核心所在。 查了一些设定发现,对于龙芯的北桥设定有问题,导致 MIPS CPU的 IP2和IP3一直被set,也就是一直认为有中断发生。 由于在 sys_open((const char __user *) "/dev/console", O_RDWR, 0) 前,串口并没有使用中断因此没有问题。 但是一旦 串口采用中断了以后,立刻问题就出现了。 重新设置了龙芯的北桥后,问题解决。 

两个结论: 
(1) 要抓住问题的核心。有点时候kernel的错误信息有一些误导。 
(2) 串口只有在进入user land前才使用中断。 

结贴。 


作者:unbutun 发表于2011-7-10 12:46:06 原文链接
阅读:6 评论:0 查看评论
ELF Bin 文件查看 ELF Bin 文件区别
 
嵌入式开发的时候,我们的编译一个*.S文件,并最终生成bin文件,编译命令大致如下:

CC=arm-softfloat-linux-gnu-gcc

LD=arm-softfloat-linux-gnu-ld

OBJCOPY=arm-softfloat-linux-gnu-objcopy

$(CC) -g $(CFLAG) -c boot.S     #先将boot.S文件生成boot.o

$(LD) -g -Bstatic -T$(LDFILE) -Ttext 0x12345600 boot.o --start-group -Map boot.map -o boot.elf    #再将boot.o生成boot.elf, boot.elf通常就是可执行文件,类似于gcc -o test test.c 中的test文件,在Linux Shell下输入./test就可以执行。

$(OBJCOPY) -O binary boot.elf boot.bin    #接着将boot.elf->boot.bin,这样可以缩小代码尺寸。

运行arm-softfloat-linux-gnu-objdump -h boot.elf 可以查看该文件的信息,但是如果变成

arm-softfloat-linux-gnu-objdump -h boot.bin会提示错误,为了看boot.bin文件信息,输入:

arm-softfloat-linux-gnu-objdump -h -b binary -m arm boot.bin就可以了。哈哈我也是现学了一招,其中参数-h可以被替换成为-D, -S, -s等等,请用arm-softfloat-linux-gnu-objdump --help查看器中表示的意义。

说说ELF Bin 文件区别:

我们有了Linux OS,为了运行可执行文件,他们是遵循ELF格式的,通常gcc -o test test.c,生成的test文件就是ELF格式的,这样就可以运行了。

arm-softfloat-linux-gnu-objcopy命令将去掉ELF格式的东西,仅仅保留最纯的汇编(不知道如何解释),

在Embedded中,如果上电开始运行,没有OS系统,如果将ELF格式的文件烧写进去,包含一些ELF格式的东西,arm运行碰到这些指令,就会导致失败,如果用arm-softfloat-linux-gnu-objcopy生成纯粹的汇编,程序就可以一步一步运行。

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/yudingding6197/archive/2009/02/06/3866140.aspx

作者:unbutun 发表于2011-7-10 12:17:48 原文链接
阅读:2 评论:0 查看评论
vmlinux,vmlinuz,zImage,bzImage几个内核文件有啥区别,哪位聊聊,谢谢!
 我在cirrus网站下载的内核及补丁文件,编译后看到三个内核文件,分别如下: 
-rwxrwxr-x 1 wmb wmb 2066336 3月 9 14:22 vmlinux 
-rwxrwxr-x 1 wmb wmb 840572 3月 9 14:22 arch/arm/boot/compressed/vmlinux 
-rwxrwxr-x 1 wmbwmb 800208 3月 9 14:22 arch/arm/boot/zImage 
不知道有什么不同,而且前两个文件名称一样,大小却不同. 

 

vmlinux 是ELF文件,即编译出来的最原始的文件。 
vmlinuz应该是vmlinux经过objcopy处理的文件,应该是个bin文件(不太确定了) 
zImage是vmlinuz经过gzip压缩后的文件 
bzImage是vmlinuz经过bzip压缩后的文件 

上面我说的有误,不好意思。 
vmlinuz应该是由ELF文件vmlinux经过OBJCOPY后,并经过压缩后的文件。

 

 

作者:unbutun 发表于2011-7-10 12:16:54 原文链接
阅读:2 评论:0 查看评论
A few examples of how to use :g in vim
VimTip 227: Power of :g
http://vim.sourceforge.net/tip_view.php?tip_id=

:g is something very old and which is very powerful. I just wanted to illustrate the use of it
with some examples. Hope, it will be useful for someone.

Brief explanation for ":g"
-------------------------
Syntax is:
    :[range]:g//[cmd]
You can think the working as, for the range (default whole file), execute
the colon command(ex) "cmd" for the lines matching . Also, for all
lines that matched the pattern, "." is set to that particular line (for
certain commands if line is not specified "." (current line) is assumed).

Some examples
-------------
Display context (5 lines) for all occurences of a pattern
    :g//z#.5
    :g//z#.5|echo "=========="
    << same as first, but with some beautification >>
Delete all lines matching a pattern
    :g//d
Delete all blank lines (just an example for above)
    :g/^s*$/d
Double space the file
    :g/^/pu ="n"
    :g/^/pu _
    << the above one also works >>
Copy all lines matching a pattern to end of file
    :g//t$
Yank all lines matching a pattern to register 'a'
    0"ay0:g//y A
Increment the number items from current line to end-of-document by one
    :.,$g/^d/exe "normal! "
Comment (C) lines containing "DEBUG" statements
    g/^s*DEBUG/exe "norm! I/* A */"
A Reverse lookup for records
(eg: An address book, with Name on start-of-line and fields after a space)
    :g/?^w?p               "if only name is interested
    :g//ka|?^w?p|'ap       "if name and the lookup-line is interested
    :g//?^w?|+,/^[^ ]/-1p  "if entire record is interested
Reverse a file (just to show the power of 'g')
    :g/^/m0

Foot note 1: use :v to negate the search pattern
Foot note 2: Some explanation of commonly used commands with :g
 :2,8co15 => Copy lines 2 through 8 after line 15
 :4,15t$  => Copy linesa 4 through 15 towards end of document (t == co)
    :-t$  => Copy previous line to end of document
     :m0  => Move current line to the top of the document
:.,+3m$-1 => Move current line through cur-line+3 to the last but one line
             of the document
Foot note 3: Commands used with :g are ex commands, so a help search should
             be,
                :help :
                eg. :help :k

g in vim

 

 

 

 

作者:unbutun 发表于2011-7-9 10:20:39 原文链接
阅读:1 评论:0 查看评论
Bash Brace Expansion Tutorial: 6 Examples of Expanding Expressions within Braces
 One of the operation of the shell when it analyzes the input is Shell expansion. Bash provides different types of expansion. In this article let us review an important expansion — “Brace expansion”.
This article is part of our on-going Bash Tutorial series.

Brace Expansion

Brace expansion is used to generate arbitrary strings. Brace expansion allows you to create multiple modified command line arguments out of a single argument. The specified strings are used to generate all possible combination with the optional surrounding preambles and postscripts. The preamble is prefixed to each string contained within the braces, and the postscript is then appended to each resulting string, expanding left to right.

$ echo last{mce,boot,xorg}.log
lastmce.log lastboot.log lastxorg.log

where last is Preamble and .log is the postscript
The above echo statement avoids you to specifying the three log files separately. If you want to view the content of the last boot log, mce log and xorg log you can use the brace expansion as shown in the above echo statement.

1. Example for Backup using brace expansion

$ cat bkup.sh
set -x # expand the commands
da=`date +%F`
cp $da.log{,.bak}

$ ./bkup.sh
++ date +%F
+ da=2010-05-28
+ cp 2010-05-28.log 2010-05-28.log.bak
In the above backup script, it copies the current date log file with the extension .bak. The first element is empty in the braces, so first element will have only preamble.

2. Example for Restore using brace expansion

$ cat restore.sh
set -x # expand the commands
da=`date +%F`
cp $da.log{.bak,}

$ ./restore.sh
++ date +%F
+ da=2010-05-28
+ cp 2010-05-28.log.bak 2010-05-28.log
In the restore script, the first element in the parameter is .bak where as second element is empty.

Also, refer to our earlier article on bash shell functions for additional reading.
3. Example for Brace Expansion without preamble and postscript

If there is no preamble and postscript, it just expands the elements given in the braces.

$ cat expand.sh
echo {oct,hex,dec,bin}

$ ./expand.sh
oct hex dec bin
Without the optional preamble and postscript strings, the result is just a space separated list of the given strings

Brace expansion for Ranges

Brace expansion expands the sequences also. The sequences can be of integers or characters.

4. Example for Integer and character sequences

$ cat sequence.sh
cat /var/log/messages.{1..3}
echo {a..f}{1..9}.txt

$ ./sequence.sh
May  9 01:18:29 x3 ntpd[2413]: time reset -0.132703 s
May  9 01:22:38 x3 ntpd[2413]: synchronized to LOCAL(0), stratum 10
May  9 01:23:44 x3 ntpd[2413]: synchronized to
May  9 01:47:48 x3 dhclient: DHCPREQUEST on eth0
May  9 01:47:48 x3 dhclient: DHCPACK from 23.42.38.201
..
..
a1.txt a2.txt a3.txt a4.txt b1.txt b2.txt b3.txt b4.txt c1.txt c2.txt c3.txt c4.txt
The first cat command, expands messages.1,messages.2 and messages.3 and displays the content. and in the next echo statement character and integer sequences are combined and used.

Sequences with increment value

In kshell brace expansion, you can use increment value, to generate the sequences.

Syntax:
....
incr is numeric. You can use a negative integer, but the correct sign is deduced from the order of start and end.

5. Example for using Increment in sequences

$ ksh
$ echo /var/log/messages.{1..7..2}
/var/log/messages.1 /var/log/messages.3 /var/log/messages.5 /var/log/messages.7
$
Using this you could see the alternate days logfiles.

Pitfall in Brace expansion

Brace expansion does not expand bash variables, because the brace expansion is the very first step of the shell expansion, variable will be expanded later.

6. Example for Variables in expansion

If you see the output of the following two for statement, you could identify the above pitfall.

$ cat var_seq.sh
# Print 1 to 4 using sequences.
for i in {1..4}
do
        echo $i
done
start=1
end=4

# Print 1 to 4 using through variables
echo "Sequences expressed using variables"
for i in {$start..$end}
do
        echo $i
done

$ ./var_seq.sh
1
2
3
4
Sequences expressed using variables
{1..4}
作者:unbutun 发表于2011-7-9 8:38:12 原文链接
阅读:1 评论:0 查看评论
使用VIM开发软件项目 - (11) 剑不离手:quickfix
 
本节所用命令的帮助入口:

:help quickfix
:help :make
:help 'makeprg'
:help 'errorformat'
:help 'switchbuf'
:help location-list
:help grep
:help :vimgrep
:help :grep
:help starstar-wildcard
以前读武侠小说,看到武林高手们都是从来剑不离手的。使用VIM开发软件项目,你也可以做到这一点,:-)

VIM由一个程序员开发,而且为更多的程序所使用,所以在VIM中加强了对软件开发的支持,quickfix模式的引入就是一个例子。所谓quickfix模式,它和Normal模式、Insert模式没什么关系,它只是一种加速你开发的工作方式。

Quickfix模式的主要思想是保存一个位置列表,然后提供一系列命令,实现在这个位置列表中跳转。

位置列表的产生可以从编译器的编译输出信息中获得,也可以由grep命令的输出信息中获得,我们上篇文章所介绍的cscope命令,也可以产生位置列表信息(:help 'cscopequickfix')。

[编译]

通常,我们在开发过程中,经常要写代码,编译,修改编译错误,这个过程会数十遍上百遍的重复。如果你是根据编译器输出的错误信息,打开出错的文件,找到出错的行,然后再开始修改,那效率未免太低下了。

利用VIM的quickfix模式,可以大大加快这一过程,你可以在VIM启动编译,然后VIM会根据编译器输出的错误信息,自动跳到第一个出错的地方,让你进行修改;修改完后,使用一个快捷键,跳到下一个错误处,再进行修改,方便的很。

为了做到这一点,你首先要定义编译时所使用的程序,对大多数使用Makefile的项目来说,VIM的缺省设置“make”已经可以满足要求了。如果你的项目需要用一个特殊的程序进行编译,就需要修改'makeprg'选项的值。

大家在学编程时大概都见到过"hello world"程序,我们就以这个简单的例子为例,讲一下quickfix模式的用法。

该程序的内容如下,里面包含了三个小小的错误:

/* hello world demo */

#include   :w:make
nmap cn :cn
nmap cp :cp
nmap cw :cw 10
现在使用“,”就可以编译,使用“,cp”和“,cn”跳到上一个和下一个错误,使用“,cw”来打开一个quickfix窗口。这下顺手多了!

如果你希望跳转到出错的文件时,使用一个分隔的窗口打开,请参阅'switchbuf'选项的值。

在VIM7中,每个窗口都可以拥有自己的位置列表,这样,你就能够同时打开多个位置列表了,而quickfix列表在整个VIM中只有一个。你可以使用位置列表来显示编译错误信息,具体命令参阅手册:“:help location-list”以及“:help :lmake”

作者:unbutun 发表于2011-7-7 20:01:15 原文链接
阅读:2 评论:0 查看评论
VIM下,在文件及目录中查找字符串的方法 (vimgrep)
 以前用editplus的时候,有一个在文件中查找的功能,可以在所有打开的文件中查找字符串,也可以在某一个目录及它的子目录中查找.那么在VIM中是否也有相关的功能呢?答案当然是肯定的.VIM中有个类似grep的命令,叫做vimgrep,语法如下:
:vimgrep /{pattern}/[g][j] {file} ...


    简单来讲,就是在路径和文件命符合{file}的所有文件中,查找符合{pattern}的字符串.(查找的结果可以用:copen命令打开quickfix列表查看).
    没有参数g的话,则行只查找一次关键字.反之会查找所有的关键字.
    没有参数j的话,查找后,VIM会跳转至第一个关键字所在的文件.反之,只更新结果列表(quickfix).

    洒家再给几个例子,比方
:vimgrep /the menu/ *.php

表示在当前目录下的扩展名为php的所有文件中,查找字符串"the menu".

:vimgrep /the menu/ ./includes/*.*

表示在当前目录中的"includes"目录里的所有文件中,查找字符串"the menu".

如果要在当前目录及其子目录中查找怎么办呢?也好办
:vimgrep /the menu/ **/*.*

用这句就可以了.

    查找时{pattern}可用正则表达式,使用起来和'/'命令是一样的,就不多说了.

    查找的结果可以用":copen"命令查看,在列表里,将光标移动至相应的位置,按回车就打开对应的文件了.
    注:

        :copen    打开quickfix

        :cclose    关闭quickfix

        :cc    是在转到当前查找到的位置
        :cn    转到下一个位置
        :cp    转到前一个位置


    当然,用grep同样可以达到这个效果,不过用vimgrep的好处就是与系统无关,能适用于所有系统的VIM,而且能自动识别文件编码和换行.嘿嘿,VIM就是你用的越多就越能感觉到它的强大了.

/转载请注明出处与链接,谢谢! http://www.nerdlinux.com/post/43
作者:unbutun 发表于2011-7-7 19:58:20 原文链接
阅读:2 评论:0 查看评论
Makefile 简明手册
一个完整的 Makefile 通常由 "显式规则"、"隐式规则"、"变量定义"、"指示符"、"注释" 五部分组成。

显式规则: 描述了在何种情况下如何更新一个或多个目标文件。
隐式规则: make 默认创建目标文件的规则。(可重写)
变量定义: 类似 shell 变量或 C 宏,用一个简短名称代表一段文本。
指示符: 包括包含(include)、条件执行、宏定义(多行变量)等内容。
注释: 字符 "#" 后的内容被当作注释。
(1) make 会在工作目录按照 "GNUmakefile、makefile、Makefile(推荐)" 顺序查找并执行,或使用 "-f" 参数显式指定文件名。
(2) 如果不在 make 命令行显式指定目标规则名,则默认使用第一个有效规则。
(3) Makefile 中 "{1}quot;、"#" 有特殊含义,可以进行转义 "/#"、"$"。
(4) 可以使用 "/" 换行(注释行也可以使用),但其后不能有空格,新行同样必须以 Tab 开头和缩进。

注意: 本文中提到的目标文件通常是 ".o",类似的还有源文件(.c)、头文件(.h) 等。

1. 规则

规则组成方式:

target...: prerequisites...
    command
    ...
target: 目标。
prerequisites: 依赖列表。一个或多个文件名列表(空格分隔,通常是 ".o, .c, .h" 等,可以使用通配符)。
command: 命令行。shell 命令或程序,且必须以 Tab 开头(最容易犯的错误)。
没有命令行的规则只能指示 "依赖关系",没有依赖项的规则指示 "如何" 构建目标,而非 "何时" 构建。
目标的依赖列表可以通过 GCC -MM 参数获得。

规则处理方式:

(1) 目标文件不存在,使用其规则(显式或隐式规则)创建。
(2) 目标文件存在,但如果任何一个依赖文件比目标文件修改时间 "新",则重新创建目标文件。
(3) 目标文件存在,且比所有依赖文件 "新",则什么都不做。

1.1 隐式规则

当我们不编写显式规则时,隐式规则就会生效。当然我们可以修改隐式规则的命令。

%.o: %.c
    $(CC) $(CFLAGS) -o $@ -c {1}lt;

未定义规则或者不包含命令的规则都会使用隐式规则。

# 隐式规则
%.o: %.c
    @echo {1}lt;
    @echo $^
    $(CC) $(CFLAGS) -o $@ -c {1}lt;

all: test.o main.o
    $(CC) $(CFLAGS) $(LDFLAGS) -o $(OUT) $^

main.o: test.o test.h

输出:

$ make

./lib/test.c
./lib/test.c
gcc -Wall -g -std=c99 -I./lib -I./src -o test.o -c ./lib/test.c

./src/main.c
./src/main.c test.o ./lib/test.h
gcc -Wall -g -std=c99 -I./lib -I./src -o main.o -c ./src/main.c

gcc -Wall -g -std=c99 -I./lib -I./src -lpthread  -o test test.o main.o

"test.o" 规则不存在,直接使用了隐式规则。"main.o" 没有命令,使用隐式规则的同时,还会合并依赖列表。

1.2 模式规则

在隐式规则前添加特定的目标,就形成了模式规则。

test.o main.o: %.o: %.c
    $(CC) $(CFLAGS) -o $@ -c {1}lt;

1.3 搜索路径

在实际项目中我们通常将源码文件分散在多个目录中,将这些路径写入 Makefile 会很麻烦,此时可以考虑用 VPATH 变量指定搜索路径。

all: lib/test.o src/main.o
    $(CC) $(CFLAGS) $(LDFLAGS) -o $(OUT) $^

改写成 VPATH 方式后,要调整项目目录就简单多了。

# 依赖目标搜索路径
VPATH = ./src:./lib

# 隐式规则
%.o:%.c
    -@echo "source file: {1}lt;"
    $(CC) $(CFLAGS) -o $@ -c {1}lt;

all:test.o main.o
    $(CC) $(CFLAGS) $(LDFLAGS) -o $(OUT) $^

执行:

$ make

source file: ./lib/test.c
gcc -Wall -g -std=c99 -I./lib -I./src -o test.o -c ./lib/test.c

source file: ./src/main.c
gcc -Wall -g -std=c99 -I./lib -I./src -o main.o -c ./src/main.c

gcc -Wall -g -std=c99 -I./lib -I./src -lpthread  -o test test.o main.o

还可使用 make 关键字 vpath。比 VPATH 变量更灵活,甚至可以单独为某个文件定义路径。

vpath %.c ./src:./lib # 定义匹配模式(%匹配任意个字符)和搜索路径。
vpath %.c # 取消该模式
vpath # 取消所有模式

相同的匹配模式可以定义多次,make 会按照定义顺序搜索这多个定义的路径。

vpath %.c ./src
vpath %.c ./lib
vpath %.h ./lib

1.4 伪目标

当我们为了执行命令而非创建目标文件时,就会使用伪目标了,比如 clean。伪目标总是被执行。

clean:
    -rm *.o
    
.PHONY: clean

使用 "-" 前缀可以忽略命令错误,".PHONY" 的作用是避免和当前目录下的文件名冲突(可能引发隐式规则)。

2. 命令

每条命令都在一个独立 shell 环境中执行,如希望在同一 shell 执行,可以用 ";" 将命令写在一行。

test:
    cd test; cp test test.bak

提示: 可以用 "/" 换行,如此更美观一些。

默认情况下,多行命令会顺序执行。但如果命令出错,默认会终止后续执行。可以添加 "-" 前缀来忽略命令错误。另外还可以添加 "@" 来避免显示命令行本身。

all: test.o main.o
    @echo "build ..."
    @$(CC) $(CFLAGS) $(LDFLAGS) -o $(OUT) $^

执行其他规则:

all: test.o main.o
    $(MAKE) info
    @$(CC) $(CFLAGS) $(LDFLAGS) -o $(OUT) $^

info:
    @echo "build..."

3. 变量

Makefile 支持类似 shell 的变量功能,相当于 C 宏,本质上就是文本替换。
变量名区分大小写。变量名建议使用字母、数字和下划线组成。引用方式 "$(var)" 或 "${var}"。
引用未定义变量时,输出空。

3.1 变量定义

首先注意的是 "=" 和 ":=" 的区别。

"=": 递归展开变量,仅在目标展开时才会替换,也就是说它可以引用在后面定义的变量。
":=": 直接展开变量,在定义时就直接展开,它无法后置引用。
A = "a: $(C)"
B := "b: $(C"
C = "haha..."

all:
    @echo $A
    @echo $B

输出:

$ make

a: haha...
b:

由于 B 定义时 C 尚未定义,所以直接展开的结果就是空。修改一下,再看。

C = "none..."
A = "a: $(C)"
B := "b: $(C)"
C = "haha..."

all:
    @echo $A
    @echo $B

输出:

$ make

a: haha...
b: none...

可见 A 和 B 的展开时机的区别。

除了使用 "="、":=" 外,还可以用 "define ... endef" 定义多行变量(宏,递归展开,只需在调用时添加 "@" 即可)。

define help
    echo ""
    echo "  make release : Build release version."
    echo "  make clean   : Clean templ files."
    echo ""
endef

debug:
    @echo "Build debug version..."
    @$(help)
    @$(MAKE) $(OUT) DEBUG=1

release:
    @echo "Build release version..."
    @$(help)
    @$(MAKE) clean $(OUT)

3.2 操作符

"?=" 表示变量为空或未定义时才进行赋值操作。

A = "a"

A ?= "A"
B ?= "B"

all:
    @echo $A
    @echo $B

输出:

$ make

a
B

"+=" 追加变量值。注意变量展开时机。

A = "$B"
A += "..."
B = "haha"

all:
    @echo $A

输出:

$ make

haha ...

3.3 替换引用

使用 "$(VAR:A=B)" 可以将变量 VAR 中所有以 "A" 结尾的单词替换成以 "B" 结尾。

A = "a.o b.o c.o"

all:
    @echo $(A:o=c)

输出:

$ make

a.c b.c c.o

3.4 命令行变量

命令行变量会替换 Makefile 中定义的变量值,除非使用 override。

A = "aaa"
override B = "bbb"
C += "ccc"
override D += "ddd"

all: 
    @echo $A
    @echo $B
    @echo $C
    @echo $D

执行:

$ make A="111" B="222" C="333" D="444"

111
bbb
333
444 ddd

我们注意到追加方式在使用 override 后才和命令行变量合并。

3.5 目标变量

仅在某个特定目标中生效,相当于局部变量。

test1: A = "abc"

test1:
    @echo "test1" $A

test2:
    @echo "test2" $A

输出:

$ make test1 test2

test1 abc
test2

还可以定义模式变量。

test%: A = "abc"

test1:
    @echo "test1" $A

test2:
    @echo "test2" $A

输出:

$ make test1 test2

test1 abc
test2 abc

3.5 自动化变量

$?: 比目标新的依赖项。
$@: 目标名称。
{1}lt;: 第一个依赖项名称(搜索后路径)。
$^: 所有依赖项(搜索后路径,排除重复项)。
3.6 通配符

在变量定义中使用通配符则需要借助 wildcard。

FILES = $(wildcard *.o)

all:
    @echo $(FILES)

3.7 环境变量

和 shell 一样,可以使用 "export VAR" 将变量设定为环境变量,以便让命令和递归调用的 make 命令能接收到参数。

例如: 使用 GCC C_INCLUDE_PATH 环境变量来代替 -I 参数。

C_INCLUDE_PATH := ./lib:/usr/include:/usr/local/include
export C_INCLUDE_PATH
4. 条件

没有条件判断是不行滴。
CFLAGS = -Wall -std=c99 $(INC_PATHS)
ifdef DEBUG
    CFLAGS += -g
else
    CFLAGS += -O3
endif


类似的还有: ifeq、ifneq、ifndef
格式: ifeq (ARG1, ARG2) 或 ifeq "ARG1" "ARG2"

# DEBUG == 1
ifeq "$(DEBUG)" "1"
    ...
else
    ...
endif

# DEBUG 不为空
ifneq ($(DEBUG), )
    ...
else
    ...
endif


实际上,我们可以用 if 函数来代替。相当于编程语言中的三元表达式 "?:"。

CFLAGS = -Wall $(if $(DEBUG), -g, -O3) -std=c99 $(INC_PATHS)


5. 函数

*nix 下的 "配置" 都有点 "脚本语言" 的感觉。
make 支持函数的使用,调用方法 "$(function args)" 或 "${function args}"。多个参数之间用 "," (多余的空格可能会成为参数的一部分)。

例如: 将 "Hello, World!" 替换成 "Hello, GNU Make!"。

A = Hello, World!
all:
    @echo $(subst World, GUN Make, $(A))


注意: 字符串没有用引号包含起来,如果字符串中有引号字符,使用 "/" 转义。

5.1 foreach

这个 foreach 很好,执行结果输出 "[1] [2] [3]"。

A = 1 2 3
all:
    @echo $(foreach x,$(A),[$(x)])


5.2 call

我们还可以自定义一个函数,其实就是用一个变量来代替复杂的表达式,比如对上面例子的改写。

A = x y z
func = $(foreach x, $(1), [$(x)])
all:
    @echo $(call func, $(A))
    @echo $(call func, 1 2 3)


传递的参数分别是 "$(1), $(2) ..."。

用 define 可以定义一个更复杂一点的多行函数。

A = x y z
define func
    echo "$(2): $(1) -> $(foreach x, $(1), [$(x)])"
endef

all:
    @$(call func, $(A), char)
    @$(call func, 1 2 3, num)


输出:
$ make

 char:  x y z ->  [x]  [y]  [z]
 num:  1 2 3 ->  [1]  [2]  [3]


5.3 eval

eval 函数的作用是动态生成 Makefile 内容。

define func
    $(1) = $(1)...
endef

$(eval $(call func, A))
$(eval $(call func, B))

all:
    @echo $(A) $(B)


上面例子的执行结果实际上是 "动态" 定义了两个变量而已。当然,借用 foreach 可以更紧凑一些。

$(foreach x, A B, $(eval $(call func, $(x))))


5.4 shell

执行 shell 命令,这个非常实用。

A = $(shell uname)
all:
    @echo $(A)


更多的函数列表和详细信息请参考相关文档。

6. 包含

include 指令会读取其他的 Makefile 文件内容,并在当前位置展开。
通常使用 ".mk" 作为扩展名,支持文件名通配符,支持相对和绝对路径。

7. 执行

Makefile 常用目标名: 

all: 默认目标。

clean: 清理项目文件的伪目标。

install: 安装(拷贝)编译成功的项目文件。

tar: 创建源码压缩包。

dist: 创建待发布的源码压缩包。

tags: 创建 VIM 使用的 CTAGS 文件。


make 常用命令参数: 

-n: 显示待执行的命令,但不执行。

-t: 更新目标文件时间戳,也就是说就算依赖项被修改,也不更新目标文件。

-k: 出错时,继续执行。

-B: 不检查依赖列表,强制更新目标。

-C: 执行 make 前,进入特定目录。让我们可以在非 Makefile 目录下执行 make 命令。

-e: 使用系统环境变量覆盖同名变量。

-i: 忽略命令错误。相当于 "-" 前缀。

-I: 指定 include 包含文件搜索目录。

-p: 显示所有 Makefile 和 make 的相关参数信息。

-s: 不显示执行的命令行。相当于 "@" 前缀。


顺序执行多个目标:

$ make clean debug

作者:unbutun 发表于2011-6-28 21:55:00 原文链接
阅读:5 评论:0 查看评论
Ubuntu/Linux Tips (shell 快捷键)
收集常用技巧,备忘。
不定期更新。

1. 快捷键

终端快捷键:

CTRL + C: 停止
CTRL + Z: 切换到后台
CTRL + D: 注销当前会话
CTRL + W: 删除光标前的命令参数
CTRL + U: 删除光标前的所有字符
CTRL + K: 删除光标后的所有字符
CTRL + A: 将光标移到最前
CTRL + E: 将光标移到末尾
CTRL + L: 清屏
CTRL + R: 搜索历史命令

2. 文件管理

文件搜索:

$ find . -name "*.py[co]"                      # 按通配符搜索
$ find . -iregex '.*/index.*'                  # 使用正则表达式搜索 (包含完整路径匹配,区分大小写用 regex)
$ find . -type d                               # 搜索目录类型 (类型 f, d ...)
$ find . -type f -exec ls -l {} /;             # 查找并直接执行命令
$ find . -type f -perm +0100 | xargs ls -l     # 查找具有执行权限的普通文件
$ find . -name "*.py" | xargs grep -n main     # 按内容搜索
$ find . -name "*.py[co]" | xargs rm -rf       # 批量删除
$ find . -type f -size +10k | xargs ls -lh     # 大于10kb的文件 (单位 k, M, G, T, P)。
$ find . -type f -mtime -2d | xargs ls -l      # 最近两天被修改的文件 (单位 s, m, h, d, w),没有被修改使用 +2d。

查看文件头/尾 n 行:

$ head -n 5 test.txt
$ tail -n 5 test.txt

显示/分页显示文件内容:

$ cat test.txt
$ less test.txt

实时刷新文件内容变更(适合监控日志文件变化,调试的时候很有用):

$ tail -f test.txt
$ less +F test.txt

查看文件类型:

$ file test.txt

3. 系统管理

后台运行程序,不随终端会话关闭: nohup

$ nohup cat a.txt &
$ nohup cat a.txt >/dev/null 2>&1 &

终止进程: kill killall

$ kill 1267 1268 1269
$ kill -INT 1267
$ killall python
$ killall -INT python

4. 网络管理

显示网络状态: netstat

$ netstat -lp   # 显示监听
$ netstat -lpn  # 显示监听端口
$ netstat -t    # 显示当前连接

动态查看网站路由: mtr

$ mtr www.rainsts.net

DNS 查询: dig

$ dig www.rainsts.net

IP 地址配置: ifconfig

$ ifconfig
$ ip a

简易 TCP 监听和连接测试工具(可双向发送数据): nc

$ nc -l 8000         # 监听
$ nc localhost 8000  # 客户端

5. 系统安全

6. 压缩备份

压缩/接压缩: tar

$ tar czf test.tar.gz ./test
$ tar czf test.tar.gz a.txt b.txt c.txt  # 压缩多个路径
$ tar tf test.tar.gz                     # 查看压缩包内容
$ tar xf test.tar.gz
$ tar xf test.tar.gz -C ~/test           # 解压缩到指定目录

7. 系统帮助

系统手册: man

$ man -k printf # 模糊搜索
$ man -f printf # 精确搜索

8. 相关软件

Putty:

CTRL + S: 屏蔽控制台输出(比如需要输入一些敏感信息)
CTRL + Q: 恢复控制台输出
作者:unbutun 发表于2011-6-28 21:49:00 原文链接
阅读:11 评论:0 查看评论
Git Commands
1. 系统设置

通常情况下,我们只需简单设置用户信息和着色即可。

$ git config --global user.name "Q.yuhen"
$ git config --global user.email [email protected]
$ git config --global color.ui true 

可以使用 "--list" 查看当前设置。

$ git config --list

2. 初始化

创建项目目录,然后执行 "git init" 初始化。这会在项目目录创建 ".git" 目录,即为元数据信息所在。

$ git init

通常我们还需要创建一个忽略配置文件 ".gitignore",并不是什么都需要加到代码仓库中的。

$ cat > .gitignore << end

> *.[oa]
> *.so
> *~
> !a.so
> test
> tmp/

> end

如果作为 Server 存在,那么可以忽略工作目录,以纯代码仓库形式存在。

$ git --bare init

在客户端,我们可以调用 "clone" 命令克隆整个项目。支持 SSH / HTTP/ GIT 等协议。

$ git clone ssh://user@server:3387/git/myproj
$ git clone git://github.com/schacon/grit.git mygrit

3. 基本操作

Git 分为 "工作目录"、"暂存区"、"代码仓库" 三个部分。

(1) 添加

文件通过 "git add " 被添加到暂存区,如此暂存区将拥有一份文件快照。

$ git add .
$ git add file1 file2
$ git add *.c

"git add" 除了添加新文件到暂存区进行跟踪外,还可以刷新已被跟踪文件的快照。需要注意的是,被提交到代码仓库的是暂存区的快照,而不是工作目录中的文件。

(2) 提交

"git commit -m " 命令将暂存区的快照提交到代码仓库。

$ git commit -m "message"

在执行 commit 提交时,我们通常会直接使用 "-a" 参数。该参数的含义是:刷新暂存区快照,提交时同时移除被删除的文件。但该参数并不会添加未被跟踪的新文件,依然需要执行 "git add " 操作。

$ git commit -am "message"

(3) 状态

可以使用 "git status" 查看暂存区状态,通常包括 "当前工作分支(Branch)"、"被修改的已跟踪文件(Changed but not updated)",以及 "未跟踪的新文件(Untracked files)" 三部分信息。

$ git status

# On branch master

# Changed but not updated:
#   (use "git add ..." to update what will be committed)
#   (use "git checkout -- ..." to discard changes in working directory)
#
#       modified:   readme
#

# Untracked files:
#   (use "git add ..." to include in what will be committed)
#
#       install
no changes added to commit (use "git add" and/or "git commit -a")

(4) 比较

要比较三个区域的文件差别,需要使用 "git diff" 命令。

使用 "git diff [file]" 查看工作目录和暂存区的差异。
使用 "git diff --staged [file]" 或 "git diff --cached [file]" 查看暂存区和代码仓库的差异。

$ git diff readme

diff --git a/readme b/readme
index e69de29..df8285e 100644
--- a/readme
+++ b/readme
@@ -0,0 +1,2 @@
+1111111111111111111
+

(5) 撤销

作为代码管理工作,我们随时可以 "反悔"。

使用 "git reset HEAD " 命令可以取消暂存区的文件快照(即恢复成最后一个提交版本),这不会影响工作目录的文件修改。
使用 "git checkout -- " 可以使用暂存区快照恢复工作目录文件,工作目录的文件修改被抛弃。

$ git chekcout -- readme

在 Git 中 "HEAD" 表示仓库中最后一个提交版本,"HEAD^" 是倒数第二个版本,"HEAD~2" 则是更老的版本。

我们可以直接 "签出" 代码仓库中的某个文件版本到工作目录,该操作同时会取消暂存区快照。

$ git checkout HEAD^ readme

如果想将整个项目回溯到以前的某个版本,可以使用 "git reset"。可以选择的参数包括默认的 "--mixed" 和 "--hard",前者不会取消工作目录的修改,而后者则放弃全部的修改。该操作会丢失其后的日志。

$ git reset --hard HEAD^

(6) 日志

每次提交都会为整个项目创建一个版本,我们可以通过日志来查看相关信息。

参数 "git log -p" 可以查看详细信息,包括修改的内容。
参数 "git log -2" 查看最后两条日志。
参数 "git log --stat" 可以查看统计摘要。

$ git log --stat -2 -p

commit c11364da1bde38f55000bc6dea9c1dda426c00f9
Author: Q.yuhen 
Date:   Sun Jul 18 15:53:55 2010 +0800

    b
---
 0 files changed, 0 insertions(+), 0 deletions(-)

diff --git a/install b/install
new file mode 100644
index 0000000..e69de29

commit 784b289acc8dccd1d2d9742d17f586ccaa56a3f0
Author: Q.yuhen 
Date:   Sun Jul 18 15:33:24 2010 +0800

    a
---
 0 files changed, 0 insertions(+), 0 deletions(-)

diff --git a/readme b/readme
new file mode 100644
index 0000000..e69de29

(7) 重做

马有失蹄,使用 "git commit --amend" 可以重做最后一次提交。

$ git commit --amend -am "b2"

[master 6abac48] b2
 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 abc
 create mode 100644 install

$ git log

commit 6abac48c014598890c6c4f47b4138f6be020e403
Author: Q.yuhen 
Date:   Sun Jul 18 15:53:55 2010 +0800

    b2

commit 784b289acc8dccd1d2d9742d17f586ccaa56a3f0
Author: Q.yuhen 
Date:   Sun Jul 18 15:33:24 2010 +0800

    a

(8) 查看

使用 "git show" 可以查看日志中文件的变更信息,默认显示最后一个版本(HEAD)。

$ git show readme 
$ git show HEAD^ readme

(9) 标签

可以使用标签(tag)对最后提交的版本做标记,如此可以方便记忆和操作,这通常也是一个里程碑的标志。

$ git tag v0.9

$ git tag
v0.9

$ git show v0.9
commit 3fcdd49fc0f0a45cd283a86bc743b4e5a1dfdf5d
Author: Q.yuhen 
Date:   Sun Jul 18 14:53:55 2010 +0800
...

可以直接用标签号代替日志版本号进行操作。

$ git log v0.9

commit 3fcdd49fc0f0a45cd283a86bc743b4e5a1dfdf5d
Author: Q.yuhen 
Date:   Sun Jul 18 14:53:55 2010 +0800

    a

4. 工作分支

用 Git 一定得习惯用分支进行工作。

使用 "git branch " 创建分支,还可以创建不以当前版本为起点的分支 "git branch  HEAD^"。
使用 "git checkout " 切换分支。

$ git branch yuhen

$ git checkout yuhen
Switched to branch 'yuhen'

$ git branch
  master
* yuhen

使用 "git chekcout -b " 一次完成分支创建和切换操作。

$ git checkout -b yuhen
Switched to a new branch 'yuhen'

$ git branch
  master
* yuhen

在分支中完成提交,然后切换回主分支进行合并(git merge)和删除(git branch -d )操作。

$ git checkout master
Switched to branch 'master'

$ git merge yuhen
Updating 6abac48..7943312
Fast-forward
 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 abc.txt

$ git branch -d yuhen
Deleted branch yuhen (was 7943312).

$ git branch
* master

附注: 如果当前工作目录有未提交的内容,直接切换到其他分支会将变更一同带入。

5. 服务器

(1) 首先克隆服务器代码仓库。

$ git clone [email protected]:/git.server/project1 # SSH

完成克隆后,可以用 origin 来代替服务器地址。使用 "git remote" 命令查看相关信息。

$ git remote
origin

$ git remote show origin
* remote origin
  Fetch URL: ...
  Push  URL: ...
  HEAD branch: master
  Remote branch:
    master tracked
  Local branch configured for 'git pull':
    master merges with remote master
  Local ref configured for 'git push':
    master pushes to master (up to date)

还可以创建新的 remote 设置。

$ git remote add project1 [email protected]:/git.server/project1

$ git remote
origin
project1

$ git remote rm project1

(2) 在将代码提交(push)到服务器之前,首先要确认相关更新已经合并(merge)到主分支(master)。还应该先从服务器刷新(pull)最新代码,以确保自己的提交不会和别人最新提交的代码冲突。

$ git pull origin master
$ git push origin master

(3) 要提交标签到服务器,需要额外操作 (先执行 git push 提交,然后再执行该指令)。

$ git push origin --tags

6. 管理

检查损坏情况。

$ git fsck

清理无用数据。

$ git gc

----------- 分隔线 -----------

整理一下各种常用的命令,便于翻阅。

作者:unbutun 发表于2011-6-28 21:48:00 原文链接
阅读:8 评论:0 查看评论
Git Server
建立一个 Git 代码共享仓库服务器。

1. 服务器

通常用 SSH 协议即可,我们应该为 Git 创建一个专用账号。

$ sudo useradd git

$ sudo passwd git
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully

创建一个用来保存代码仓库的目录,注意赋予 git 账号读写权限。

$ sudo mkdir -p /var/git.server/project1
$ cd /var/git.server

$ sudo chown git project1
$ sudo chgrp git project1

$ ls -l
total 4
drwxr-xr-x 2 git git 4096 2010-05-17 00:55 project1

初始化 project1,注意在服务器上我们无需保留工作目录,因此创建一个纯粹(bare)的代码仓库。

$ cd project1/

$ sudo su git

$ pwd
/var/git.server/project1

$ git --bare init
Initialized empty Git repository in /var/git.server/project1/

$ ls -l
total 32
drwxr-xr-x 2 git git 4096 2010-05-17 00:59 branches
-rw-r--r-- 1 git git   66 2010-05-17 00:59 config
-rw-r--r-- 1 git git   73 2010-05-17 00:59 description
-rw-r--r-- 1 git git   23 2010-05-17 00:59 HEAD
drwxr-xr-x 2 git git 4096 2010-05-17 00:59 hooks
drwxr-xr-x 2 git git 4096 2010-05-17 00:59 info
drwxr-xr-x 4 git git 4096 2010-05-17 00:59 objects
drwxr-xr-x 4 git git 4096 2010-05-17 00:59 refs

$ exit

我们在服务器上克隆一份用于管理和测试(应该禁止直接操作服务器仓库目录)。

$ git clone /var/git.server/project1/
Initialized empty Git repository in /home/yuhen/project1/.git/
warning: You appear to have cloned an empty repository.

$ ls -al project1
total 12
drwxr-xr-x  3 yuhen yuhen 4096 2010-05-17 01:02 .
drwxr-xr-x 10 yuhen yuhen 4096 2010-05-17 01:02 ..
drwxr-xr-x  7 yuhen yuhen 4096 2010-05-17 01:02 .git

我们添加点项目初始化文件。

$ cd project1

$ cat > .gitingore << end
> *~
> *.swp
> end

$ touch README

$ git status
# On branch master
#
# Initial commit
#
# Untracked files:
#   (use "git add ..." to include in what will be committed)
#
#       .gitingore
#       README
nothing added to commit but untracked files present (use "git add" to track)

$ git add .
$ git commit -am "Start"
[master (root-commit) 723471e] Start
 1 files changed, 2 insertions(+), 0 deletions(-)
 create mode 100644 .gitingore
 create mode 100644 README

我们向服务器提交第一个版本。

$ git push git@localhost:/var/git.server/project1/ master

Counting objects: 4, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (4/4), 258 bytes, done.
Total 4 (delta 0), reused 0 (delta 0)
To git@localhost:/var/git.server/project1/
 * [new branch]      master -> master

通常情况下,我们可以用 origin 来代替服务器地址,不过当前测试账号没有写 git.server/project1 的权限,因此用 ssh 路径。同时需要指定 branch。

2. 客户端

好了,现在作为一个普通程序员,我们开始为 project1 项目工作。

$ git clone [email protected]:/var/git.server/project1
Initialized empty Git repository in /home/yuhen/project1/.git/
[email protected]'s password: 
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (4/4), done.

$ ls -al project1
total 16
drwxr-xr-x  3 yuhen yuhen 4096 2010-05-17 01:11 .
drwxr-xr-x 27 yuhen yuhen 4096 2010-05-17 01:10 ..
drwxr-xr-x  8 yuhen yuhen 4096 2010-05-17 01:11 .git
-rw-r--r--  1 yuhen yuhen    9 2010-05-17 01:11 .gitingore
-rw-r--r--  1 yuhen yuhen    0 2010-05-17 01:11 README

代码已经克隆回来了,我们添加或修改一些文件。

$ touch INSTALL

$ git status
# On branch master
# Untracked files:
#   (use "git add ..." to include in what will be committed)
#
#    INSTALL
nothing added to commit but untracked files present (use "git add" to track)

$ git add .

$ git commit -am "INSTALL"
[master b85e275] INSTALL
 Committer: yuhen 
 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 INSTALL

在将代码提交(push)到服务器之前,首先要确认相关更新已经合并(merge)到 master 了,还应该先从服务器刷新(pull)最新代码,以确保自己的提交不会和别人最新提交的代码冲突。

$ git pull origin master
[email protected]'s password: 
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 2 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (2/2), done.
From 192.168.1.202:/var/git.server/project1
 * branch            master     -> FETCH_HEAD
Merge made by recursive.
 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 HISTORY

$ git push origin master
[email protected]'s password: 
Could not chdir to home directory /home/git: No such file or directory
Counting objects: 6, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 474 bytes, done.
Total 4 (delta 2), reused 0 (delta 0)
To [email protected]:/var/git.server/project1
   c4d7b1e..ee8cfb3  master -> master

$ git log
commit ee8cfb3d14eed091e6f96d60af68ec07c05ab09d
Merge: b85e275 c4d7b1e
Author: yuhen 
Date:   Mon May 17 01:17:49 2010 +0800

    Merge branch 'master' of 192.168.1.202:/var/git.server/project1

commit c4d7b1e796cf52e0b600f2c7e992f304052fa8c1
Author: Q.yuhen 
Date:   Mon May 17 01:17:26 2010 +0800

    HISTORY

commit b85e275b52812e3d9ac36da78fb8cc924380a58c
Author: yuhen 
Date:   Mon May 17 01:13:35 2010 +0800

    INSTALL

commit 723471e3421d7fdfa80bf31e5c27f5a174e95afd
Author: Q.yuhen 
Date:   Mon May 17 01:05:53 2010 +0800

    Start

我们应该避免频繁向服务器提交代码,而是在一个相对稳定的版本测试通过后再进行。
基本操作就是这些了,当然我们还可以提供只读账号或者 HTTP 访问协议,相关内容可参考《Pro Git》。

作者:unbutun 发表于2011-6-28 21:48:00 原文链接
阅读:9 评论:0 查看评论
Git Tips
1. 删除文件

除了用 "rm" 删除工作目录中的文件外,还得用 "git rm " 删除代码仓库中的文件。

$ rm INSTALL 

$ git status
# On branch master
# Changed but not updated:
#   (use "git add/rm ..." to update what will be committed)
#   (use "git checkout -- ..." to discard changes in working directory)
#
#    deleted:    INSTALL
#
no changes added to commit (use "git add" and/or "git commit -a")

$ git rm INSTALL
rm 'doc/INSTALL'

$ git commit -am "rm INSTALL"
[master 8600e47] rm INSTALL
 0 files changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 doc/INSTALL

当然,版本管理工具的一个好处就是有后悔药卖。

$ git checkout HEAD^ -- INSTALL

$ ls
INSTALL  README

$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD ..." to unstage)
#
#    new file:   INSTALL
#

如果仅此仓库移除,但保留工作目录中的文件,可以直接用 "git rm --cached ",遗留的文件会变成未跟踪状态。

2. 移动文件

和删除文件的做法类似。

$ mv HISTORY doc/

$ git status
# On branch master
# Changed but not updated:
#   (use "git add/rm ..." to update what will be committed)
#   (use "git checkout -- ..." to discard changes in working directory)
#
#    deleted:    HISTORY
#
# Untracked files:
#   (use "git add ..." to include in what will be committed)
#
#    doc/HISTORY
no changes added to commit (use "git add" and/or "git commit -a")

$ git add .

$ git commit -am "mv HISTORY"
[master 716af03] mv HISTORY
 1 files changed, 0 insertions(+), 0 deletions(-)
 rename HISTORY => doc/HISTORY (100%)

3. 重新提交

如果最后一次的提交需要修正什么,那么可以用 "--amend" 参数。

$ touch a.txt

$ git add .

$ git commit -am "b.txt"
[master b66690c] b.txt
 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 a.txt
yuhen@yuhen-desktop:~/myproject$ git log
commit b66690cc4353e95fa52cf6cf2e8a3210c1ace057
Author: Q.yuhen 
Date:   Tue Apr 20 19:37:23 2010 +0800

    b.txt

commit 716af03e2ebafd8c47bf941e0185b8b67adcd20e
Author: Q.yuhen 
Date:   Tue Apr 20 19:35:17 2010 +0800

    move HISTORY

很显然,注释 "b.txt" 写错了。重来吧~~~

$ git commit --amend -am "a.txt"
[master a93a7cd] a.txt
 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 a.txt

$ git log
commit a93a7cdea6d9344d975c58332064ae48b29fb68f
Author: Q.yuhen 
Date:   Tue Apr 20 19:37:23 2010 +0800

    a.txt

commit 716af03e2ebafd8c47bf941e0185b8b67adcd20e
Author: Q.yuhen 
Date:   Tue Apr 20 19:35:17 2010 +0800

    move HISTORY

最后一条提交日志被替换了。

4. 恢复单个文件

可以用 "git checkout ..." 签出以前的某个 "提交版本" 。

$ cat main.c
#include 
#include 
#include 

int main(int argc, char* argv[])
{
    printf("Hello, World!/n");
    return EXIT_SUCCESS;
}

$ git show HEAD^ main.c
commit 65bd689c3a2b97c598ee2e66c453cc526b63b892
Author: Q.yuhen 
Date:   Tue Apr 20 19:52:16 2010 +0800

    add main.c

diff --git a/main.c b/main.c
new file mode 100644
index 0000000..c50fab4
--- /dev/null
+++ b/main.c
@@ -0,0 +1,9 @@
+#include 
+#include 
+#include 
+
+int main(int argc, char* argv[])
+{
+    return EXIT_SUCCESS;
+}
+

$ git checkout HEAD^ -- main.c

$ cat main.c
#include 
#include 
#include 

int main(int argc, char* argv[])
{
    return EXIT_SUCCESS;
}

也可以用 "git reset HEAD " 重置已添加到暂存区(stage)但未提交(commit)的文件。

4. 查看文件详细信息

用 "git show" 查看某个提交版本的具体信息,或者 "git diff" 比较差异。

$ git show main.c
commit 017c106d3bb9c423dead6d732db1651119b6423c
Author: Q.yuhen 
Date:   Tue Apr 20 20:04:12 2010 +0800

    update main.c

diff --git a/main.c b/main.c
index c50fab4..a6bb490 100644
--- a/main.c
+++ b/main.c
@@ -4,6 +4,7 @@
 
 int main(int argc, char* argv[])
 {
+    printf("Hello, World!/n");
    return EXIT_SUCCESS;
 }

$ git diff HEAD main.c
diff --git a/main.c b/main.c
index a6bb490..c50fab4 100644
--- a/main.c
+++ b/main.c
@@ -4,7 +4,6 @@
 
 int main(int argc, char* argv[])
 {
-    printf("Hello, World!/n");
    return EXIT_SUCCESS;
 } 

5. 查看提交日志

"git log -3" 查看最后 3 条提交信息。

$ git log -3
commit 017c106d3bb9c423dead6d732db1651119b6423c
Author: Q.yuhen 
Date:   Tue Apr 20 20:04:12 2010 +0800

    update main.c

commit 65bd689c3a2b97c598ee2e66c453cc526b63b892
Author: Q.yuhen 
Date:   Tue Apr 20 19:52:16 2010 +0800

    add main.c

commit 3725c8d49a61bb6d0bb18d7727fcaafe32f510fc
Author: Q.yuhen 
Date:   Tue Apr 20 16:55:50 2010 +0800

    init

还可以用 "--stat" 显示简单的提交统计信息。

$ git log -3 --stat
commit 017c106d3bb9c423dead6d732db1651119b6423c
Author: Q.yuhen 
Date:   Tue Apr 20 20:04:12 2010 +0800

    update main.c

 main.c |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

commit 65bd689c3a2b97c598ee2e66c453cc526b63b892
Author: Q.yuhen 
Date:   Tue Apr 20 19:52:16 2010 +0800

    add main.c

 main.c |    9 +++++++++
 1 files changed, 9 insertions(+), 0 deletions(-)

commit 3725c8d49a61bb6d0bb18d7727fcaafe32f510fc
Author: Q.yuhen 
Date:   Tue Apr 20 16:55:50 2010 +0800

    init

 .gitignore |    5 +++++
 1 files changed, 5 insertions(+), 0 deletions(-)

参数 "-p" 显示修改的详细信息。

$ git log -1 -p
commit 017c106d3bb9c423dead6d732db1651119b6423c
Author: Q.yuhen 
Date:   Tue Apr 20 20:04:12 2010 +0800

    update main.c

diff --git a/main.c b/main.c
index c50fab4..a6bb490 100644
--- a/main.c
+++ b/main.c
@@ -4,6 +4,7 @@
 
 int main(int argc, char* argv[])
 {
+    printf("Hello, World!/n");
    return EXIT_SUCCESS;
 }

6. 初始化全局设置

用户名、联系方式以及着色显示都很要紧。

$ git config --global user.name "Q.yuhen"
$ git config --global user.email [email protected]
$ git config --global color.ui true 

可以用 "--list" 参数查看全局设置。

$ git config --list

user.name=Q.yuhen
[email protected]
color.ui=true
core.repositoryformatversion=0
core.filemode=true
core.bare=false
core.logallrefupdates=true

更多设置可参考 《Pro Git: 配置 Git》。

作者:unbutun 发表于2011-6-28 21:47:00 原文链接
阅读:7 评论:0 查看评论
Git Basics
Git 的好处还是看官方文档吧,我就不瞎扯了。我只是按照我个人的习惯简单走一下流程,记录一下。

1. 创建项目目录,建立 Git 仓库。

$ mkdir myproject

$ cd myproject

$ git init # 初始化 git 仓库
Initialized empty Git repository in /home/yuhen/myproject/.git/

从此,myproject 就是工作目录,而 git 创建的 .git 隐藏目录就是代码仓库了。

2. 建立忽略配置文件。

$ cat > .gitignore << end
> *.[oa]
> *.so
> *~
> !a.so
> test
> tmp/
> end

支持匹配符和正则表达式,支持 "#" 注释,支持用 "/" 结尾表示路径。还可以用 "!" 取反,比如前面的 "*.so" 规则忽略所有 .so 文件,然后用 "!a.so" 表示特例。balabala... 等等...

3. 好了,开始第一个 commit 吧。

$ git status # 查看当前 track 状态
# On branch master
#
# Initial commit
#
# Untracked files:
#   (use "git add ..." to include in what will be committed)
#
#    .gitignore
nothing added to commit but untracked files present (use "git add" to track)

$ git add . # 添加所有新增文件

$ git commit -am "init" # 提交必须提供一个注释,否则无法执行。
[master (root-commit) 3725c8d] init
 1 files changed, 5 insertions(+), 0 deletions(-)
 create mode 100644 .gitignore

4. 创建第一个工作分支。

$ git branch yuhen # 创建新的分支

$ git branch # 查看当前所有分支
* master
  yuhen

$ git checkout yuhen # 切换到新的工作分支
Switched to branch 'yuhen'

$ git branch # 确认一下
  master
* yuhen

也可以用 "git checkout -b yuhen" 一次完成创建和切换分支的工作。

$ git checkout -b yuhen
Switched to a new branch 'yuhen'

$ git branch
  master
* yuhen

5. 开始工作,添加或编辑文件。

$ cat > main.c << end
> #include 
> #include 
> 
> int main(int argc, char* argv[])
> {
>     return EXIT_SUCCESS;
> }
> 
> end

$ mkdir doc
$ cd doc

$ cat > README << end
> readme file
> end

$ cat > INSTALL << end
> install...
> end

$ cd ..

$ git status # 我们可以查看所有被 git 跟踪的改变
# On branch yuhen
# Untracked files:
#   (use "git add ..." to include in what will be committed)
#
#    doc/
#    main.c
nothing added to commit but untracked files present (use "git add" to track)

没问题的话,就提交到代码仓库吧。

$ git add . # 添加新增文件

$ git commit -am "main.c, doc/" # 提交
[yuhen 42d7d10] main.c, doc/
 3 files changed, 10 insertions(+), 0 deletions(-)
 create mode 100644 doc/INSTALL
 create mode 100644 doc/README
 create mode 100644 main.c

$ git log # 查看提交日志
commit 42d7d10e008f34bb6b7c9824cbfa6b1c84c008a4
Author: Q.yuhen 
Date:   Tue Apr 20 17:03:06 2010 +0800

    main.c, doc/

commit 3725c8d49a61bb6d0bb18d7727fcaafe32f510fc
Author: Q.yuhen 
Date:   Tue Apr 20 16:55:50 2010 +0800

    init

6. 合并工作分支到 master。

$ git checkout master # 切换回主分支
Switched to branch 'master'

$ git branch
* master
  yuhen

$ git merge yuhen # 将 yuhen 工作分支合并到主分支
Updating 3725c8d..42d7d10
Fast forward
 doc/INSTALL |    1 +
 doc/README  |    1 +
 main.c      |    8 ++++++++
 3 files changed, 10 insertions(+), 0 deletions(-)
 create mode 100644 doc/INSTALL
 create mode 100644 doc/README
 create mode 100644 main.c

$ git branch -d yuhen # 删除工作分支
Deleted branch yuhen (was 42d7d10).

今天的工作完成了,看看日志。

$ git log
commit 42d7d10e008f34bb6b7c9824cbfa6b1c84c008a4
Author: Q.yuhen 
Date:   Tue Apr 20 17:03:06 2010 +0800

    main.c, doc/

commit 3725c8d49a61bb6d0bb18d7727fcaafe32f510fc
Author: Q.yuhen 
Date:   Tue Apr 20 16:55:50 2010 +0800

    init

用一个分支进行工作是个好主意,因为 git 有句话叫做 "丢掉一个烂摊子总比收拾一个烂摊子强"。

7. 如果我们发现某次提交有问题,我们可以恢复到以前的某个提交版本。

$ vim main.c # 编辑文件

$ cat main.c
#include 
#include 

int main(int argc, char* argv[])
{
    printf("Hello, World!/n");
    return EXIT_SUCCESS;
}

$ git status 
# On branch master
# Changed but not updated:
#   (use "git add ..." to update what will be committed)
#   (use "git checkout -- ..." to discard changes in working directory)
#
#    modified:   main.c
#
no changes added to commit (use "git add" and/or "git commit -a")

$ git commit -am "main.c changed" # 提交
[master a79baec] main.c changed
 1 files changed, 1 insertions(+), 0 deletions(-)

$ git log # 提交成功
commit a79baecfecbcedb8545a7ff4aeebba3fe653efa4
Author: Q.yuhen 
Date:   Tue Apr 20 17:15:51 2010 +0800

    main.c changed

commit 42d7d10e008f34bb6b7c9824cbfa6b1c84c008a4
Author: Q.yuhen 
Date:   Tue Apr 20 17:03:06 2010 +0800

    main.c, doc/

commit 3725c8d49a61bb6d0bb18d7727fcaafe32f510fc
Author: Q.yuhen 
Date:   Tue Apr 20 16:55:50 2010 +0800

    init

$ git reset HEAD^ # 恢复到上次某个提交状态,可以是 HEAD^、HEAD~4、commit-id 的头几个字母,还可以是 tag。
main.c: locally modified
yuhen@yuhen-desktop:~/myproject$ git log
commit 42d7d10e008f34bb6b7c9824cbfa6b1c84c008a4
Author: Q.yuhen 
Date:   Tue Apr 20 17:03:06 2010 +0800

    main.c, doc/

commit 3725c8d49a61bb6d0bb18d7727fcaafe32f510fc
Author: Q.yuhen 
Date:   Tue Apr 20 16:55:50 2010 +0800

    init

$ git status # 看到没有,默认 reset 模式是 --mixed,会保留文件修改。还可以用 --hard 放弃这些修改。
# On branch master
# Changed but not updated:
#   (use "git add ..." to update what will be committed)
#   (use "git checkout -- ..." to discard changes in working directory)
#
#    modified:   main.c
#
no changes added to commit (use "git add" and/or "git commit -a")

$ cat main.c # 修改还被保留着呢。
#include 
#include 

int main(int argc, char* argv[])
{
    printf("Hello, World!/n");
    return EXIT_SUCCESS;
}

8. 工作了 n 天了,总算进入某个阶段性版本了。

$ git tag v0.9 # 创建简单标签

$ git tag # 显示所有标签
v0.9

$ git log v0.9 # 用标签显示提交状态
commit 42d7d10e008f34bb6b7c9824cbfa6b1c84c008a4
Author: Q.yuhen 
Date:   Tue Apr 20 17:03:06 2010 +0800

    main.c, doc/

commit 3725c8d49a61bb6d0bb18d7727fcaafe32f510fc
Author: Q.yuhen 
Date:   Tue Apr 20 16:55:50 2010 +0800

    init

$ git show --stat v0.9 # 用标签显示提交基本信息
commit 42d7d10e008f34bb6b7c9824cbfa6b1c84c008a4
Author: Q.yuhen 
Date:   Tue Apr 20 17:03:06 2010 +0800

    main.c, doc/

 doc/INSTALL |    1 +
 doc/README  |    1 +
 main.c      |    8 ++++++++
 3 files changed, 10 insertions(+), 0 deletions(-)

-------------- 分隔线 ---------------------

推荐 《Pro Git》。

作者:unbutun 发表于2011-6-28 21:46:00 原文链接
阅读:11 评论:0 查看评论
GDB 调试演示 (very good)
作为内置和最常用的调试器,GDB 显然有着无可辩驳的地位。熟练使用 GDB,就好像所有 Linux 下的开发人员建议你用 VIM 一样,是个很 "奇怪" 的情节。

测试用源代码。

#include 

int test(int a, int b)
{
    int c = a + b;
    return c;
}

int main(int argc, char* argv[])
{
    int a = 0x1000;
    int b = 0x2000;
    int c = test(a, b);
    printf("%d/n", c);

    printf("Hello, World!/n");
    return 0;
}

编译命令 (注意使用 "-g" 参数生成调试符号):

$ gcc -g -o hello hello.c

开始调试:

$ gdb hello

GNU gdb 6.8-debian
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later 
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu"...

(gdb)

1. 源码

在调试过程中查看源代码是必须的。list (缩写 l) 支持多种方式查看源码。

(gdb) l # 显示源代码

2
3    int test(int a, int b)
4    {
5        int c = a + b;
6        return c;
7    }
8
9    int main(int argc, char* argv[])
10    {
11        int a = 0x1000;
(gdb) l # 继续显示

12        int b = 0x2000;
13        int c = test(a, b);
14        printf("%d/n", c);
15
16        printf("Hello, World!/n");
17        return 0;
18    }
(gdb) l 3, 10 # 显示特定范围的源代码

3    int test(int a, int b)
4    {
5        int c = a + b;
6        return c;
7    }
8
9    int main(int argc, char* argv[])
10    {
(gdb) l main # 显示特定函数源代码

5        int c = a + b;
6        return c;
7    }
8
9    int main(int argc, char* argv[])
10    {
11        int a = 0x1000;
12        int b = 0x2000;
13        int c = test(a, b);
14        printf("%d/n", c);

可以用如下命令修改源代码显示行数。

(gdb) set listsize 50

2. 断点

可以使用函数名或者源代码行号设置断点。

(gdb) b main # 设置函数断点

Breakpoint 1 at 0x804841b: file hello.c, line 11.
(gdb) b 13 # 设置源代码行断点

Breakpoint 2 at 0x8048429: file hello.c, line 13.
(gdb) b # 将下一行设置为断点 (循环、递归等调试很有用)

Breakpoint 5 at 0x8048422: file hello.c, line 12.
(gdb) tbreak main # 设置临时断点 (中断后失效)

Breakpoint 1 at 0x804841b: file hello.c, line 11.
(gdb) info breakpoints # 查看所有断点

Num     Type           Disp Enb Address    What
2       breakpoint     keep y   0x0804841b in main at hello.c:11
3       breakpoint     keep y   0x080483fa in test at hello.c:5
(gdb) d 3 # delete: 删除断点 (还可以用范围 "d 1-3",无参数时删除全部断点)

(gdb) disable 2 # 禁用断点 (还可以用范围 "disable 1-3")

(gdb) enable 2 # 启用断点 (还可以用范围 "enable 1-3")

(gdb) ignore 2 1 # 忽略 2 号中断 1 次

当然少不了条件式中断。

(gdb) b test if a == 10

Breakpoint 4 at 0x80483fa: file hello.c, line 5.
(gdb) info breakpoints

Num     Type           Disp Enb Address    What
4       breakpoint     keep y   0x080483fa in test at hello.c:5
        stop only if a == 10

可以用 condition 修改条件,注意表达式不包含 "if"。

(gdb) condition 4 a == 30
(gdb) info breakpoints

Num     Type           Disp Enb Address    What
2       breakpoint     keep y   0x0804841b in main at hello.c:11
        ignore next 1 hits
4       breakpoint     keep y   0x080483fa in test at hello.c:5
        stop only if a == 30

3. 执行

通常情况下,我们会先设置 main 入口断点。

(gdb) b main

Breakpoint 1 at 0x804841b: file hello.c, line 11.
(gdb) r # 开始执行 (Run)

Starting program: /home/yuhen/Learn.c/hello
Breakpoint 1, main () at hello.c:11
11              int a = 0x1000;
(gdb) n # 单步执行 (不跟踪到函数内部, Step Over)

12              int b = 0x2000;
(gdb) n

13              int c = test(a, b);
(gdb) s # 单步执行 (跟踪到函数内部, Step In)

test (a=4096, b=8192) at hello.c:5
5               int c = a + b;
(gdb) finish # 继续执行直到当前函数结束 (Step Out)

Run till exit from #0  test (a=4096, b=8192) at hello.c:5
0x0804843b in main () at hello.c:13
13              int c = test(a, b);
Value returned is $1 = 12288
(gdb) c # Continue: 继续执行,直到下一个断点。

Continuing.
12288
Hello, World!

Program exited normally.

4. 堆栈

查看调用堆栈(call stack)无疑是调试过程中非常重要的事情。

(gdb) where # 查看调用堆栈 (相同作用的命令还有 info s 和 bt)

#0  test (a=4096, b=8192) at hello.c:5
#1  0x0804843b in main () at hello.c:13
(gdb) frame # 查看当前堆栈帧,还可显示当前代码

#0  test (a=4096, b=8192) at hello.c:5
5               int c = a + b;
(gdb) info frame # 获取当前堆栈帧更详细的信息

Stack level 0, frame at 0xbfad3290:
 eip = 0x80483fa in test (hello.c:5); saved eip 0x804843b
 called by frame at 0xbfad32c0
 source language c.
 Arglist at 0xbfad3288, args: a=4096, b=8192
 Locals at 0xbfad3288, Previous frame's sp is 0xbfad3290
 Saved registers:
  ebp at 0xbfad3288, eip at 0xbfad328c

可以用 frame 修改当前堆栈帧,然后查看其详细信息。

(gdb) frame 1

#1  0x0804843b in main () at hello.c:13
13              int c = test(a, b);
(gdb) info frame

Stack level 1, frame at 0xbfad32c0:
 eip = 0x804843b in main (hello.c:13); saved eip 0xb7e59775
 caller of frame at 0xbfad3290
 source language c.
 Arglist at 0xbfad32b8, args:
 Locals at 0xbfad32b8, Previous frame's sp at 0xbfad32b4
 Saved registers:
  ebp at 0xbfad32b8, eip at 0xbfad32bc

5. 变量和参数

(gdb) info locals # 显示局部变量

c = 0
(gdb) info args # 显示函数参数(自变量)

a = 4096
b = 8192

我们同样可以切换 frame,然后查看不同堆栈帧的信息。

(gdb) p a # print 命令可显示局部变量和参数值

$2 = 4096
(gdb) p/x a # 十六进制输出 (d: 十进制; u: 十进制无符号; x: 十六进制; o: 八进制; t: 二进制; c: 字符)

$10 = 0x1000
(gdb) p a + b # 还可以进行表达式计算

$5 = 12288

set variable 可用来修改变量值。

(gdb) set variable a=100

(gdb) info args
a = 100
b = 8192

6. 内存及寄存器

x 命令可以显示指定地址的内存数据。

格式: x/nfu [address]
n: 显示内存单位(组或者行)。
f: 格式 (除了 print 格式外,还有 字符串s 和 汇编 i)。
u: 内存单位 (b: 1字节; h: 2字节; w: 4字节; g: 8字节)。
(gdb) x/8w 0x0804843b # 按四字节(w)显示 8 组内存数据

0x804843b :    0x8bf04589      0x4489f045      0x04c70424      0x04853024
0x804844b :    0xfecbe808      0x04c7ffff      0x04853424      0xfecfe808
(gdb) x/8i 0x0804843b # 显示 8 行汇编指令

0x804843b :    mov    DWORD PTR [ebp-0x10],eax
0x804843e :    mov    eax,DWORD PTR [ebp-0x10]
0x8048441 :    mov    DWORD PTR [esp+0x4],eax
0x8048445 :    mov    DWORD PTR [esp],0x8048530
0x804844c :    call   0x804831c 
0x8048451 :    mov    DWORD PTR [esp],0x8048534
0x8048458 :    call   0x804832c 
0x804845d :    mov    eax,0x0
(gdb) x/s 0x08048530 # 显示字符串

0x8048530:       "%d/n"

除了通过 "info frame" 查看寄存器值外,还可以用如下指令。

(gdb) info registers # 显示所有寄存器数据

eax            0x1000   4096
ecx            0xbfad32d0       -1079168304
edx            0x1      1
ebx            0xb7fa1ff4       -1208344588
esp            0xbfad3278       0xbfad3278
ebp            0xbfad3288       0xbfad3288
esi            0x8048480        134513792
edi            0x8048340        134513472
eip            0x80483fa        0x80483fa 
eflags         0x286    [ PF SF IF ]
cs             0x73     115
ss             0x7b     123
ds             0x7b     123
es             0x7b     123
fs             0x0      0
gs             0x33     51
(gdb) p $eax # 显示单个寄存器数据

$11 = 4096

7. 反汇编

我对 AT&T 汇编不是很熟悉,还是设置成 intel 格式的好。

(gdb) set disassembly-flavor intel # 设置反汇编格式
(gdb) disass main # 反汇编函数

Dump of assembler code for function main:
0x0804840a :    lea    ecx,[esp+0x4]
0x0804840e :    and    esp,0xfffffff0
0x08048411 :    push   DWORD PTR [ecx-0x4]
0x08048414 :   push   ebp
0x08048415 :   mov    ebp,esp
0x08048417 :   push   ecx
0x08048418 :   sub    esp,0x24
0x0804841b :   mov    DWORD PTR [ebp-0x8],0x1000
0x08048422 :   mov    DWORD PTR [ebp-0xc],0x2000
0x08048429 :   mov    eax,DWORD PTR [ebp-0xc]
0x0804842c :   mov    DWORD PTR [esp+0x4],eax
0x08048430 :   mov    eax,DWORD PTR [ebp-0x8]
0x08048433 :   mov    DWORD PTR [esp],eax
0x08048436 :   call   0x80483f4 
0x0804843b :   mov    DWORD PTR [ebp-0x10],eax
0x0804843e :   mov    eax,DWORD PTR [ebp-0x10]
0x08048441 :   mov    DWORD PTR [esp+0x4],eax
0x08048445 :   mov    DWORD PTR [esp],0x8048530
0x0804844c :   call   0x804831c 
0x08048451 :   mov    DWORD PTR [esp],0x8048534
0x08048458 :   call   0x804832c 
0x0804845d :   mov    eax,0x0
0x08048462 :   add    esp,0x24
0x08048465 :   pop    ecx
0x08048466 :   pop    ebp
0x08048467 :   lea    esp,[ecx-0x4]
0x0804846a :   ret
End of assembler dump.

可以用 "b *address" 设置汇编断点,然后用 "si" 和 "ni" 进行汇编级单步执行,这对于分析指针和寻址非常有用。

8. 进程

查看进程相关信息,尤其是 maps 内存数据是非常有用的。

(gdb) help info proc stat

Show /proc process information about any running process.
Specify any process id, or use the program being debugged by default.
Specify any of the following keywords for detailed info:

  mappings -- list of mapped memory regions.
  stat     -- list a bunch of random process info.
  status   -- list a different bunch of random process info.
  all      -- list all available /proc info.
(gdb) info proc mappings # 相当于 cat /proc/{pid}/maps

process 22561
cmdline = '/home/yuhen/Learn.c/hello'
cwd = '/home/yuhen/Learn.c'
exe = '/home/yuhen/Learn.c/hello'
Mapped address spaces:

        Start Addr   End Addr       Size     Offset objfile
         0x8048000  0x8049000     0x1000          0       /home/yuhen/Learn.c/hello
         0x8049000  0x804a000     0x1000          0       /home/yuhen/Learn.c/hello
         0x804a000  0x804b000     0x1000     0x1000       /home/yuhen/Learn.c/hello
         0x8a33000  0x8a54000    0x21000  0x8a33000           [heap]
        0xb7565000 0xb7f67000   0xa02000 0xb7565000
        0xb7f67000 0xb80c3000   0x15c000          0      /lib/tls/i686/cmov/libc-2.9.so
        0xb80c3000 0xb80c4000     0x1000   0x15c000      /lib/tls/i686/cmov/libc-2.9.so
        0xb80c4000 0xb80c6000     0x2000   0x15c000      /lib/tls/i686/cmov/libc-2.9.so
        0xb80c6000 0xb80c7000     0x1000   0x15e000      /lib/tls/i686/cmov/libc-2.9.so
        0xb80c7000 0xb80ca000     0x3000 0xb80c7000
        0xb80d7000 0xb80d9000     0x2000 0xb80d7000
        0xb80d9000 0xb80da000     0x1000 0xb80d9000           [vdso]
        0xb80da000 0xb80f6000    0x1c000          0      /lib/ld-2.9.so
        0xb80f6000 0xb80f7000     0x1000    0x1b000      /lib/ld-2.9.so
        0xb80f7000 0xb80f8000     0x1000    0x1c000      /lib/ld-2.9.so
        0xbfee2000 0xbfef7000    0x15000 0xbffeb000           [stack]

9. 线程

可以在 pthread_create 处设置断点,当线程创建时会生成提示信息。

(gdb) c

Continuing.
[New Thread 0xb7e78b70 (LWP 2933)]
(gdb) info threads # 查看所有线程列表

* 2 Thread 0xb7e78b70 (LWP 2933)  test (arg=0x804b008) at main.c:24
  1 Thread 0xb7e796c0 (LWP 2932)  0xb7fe2430 in __kernel_vsyscall ()
(gdb) where # 显示当前线程调用堆栈

#0  test (arg=0x804b008) at main.c:24
#1  0xb7fc580e in start_thread (arg=0xb7e78b70) at pthread_create.c:300
#2  0xb7f478de in clone () at ../sysdeps/unix/sysv/linux/i386/clone.S:130
(gdb) thread 1 # 切换线程

[Switching to thread 1 (Thread 0xb7e796c0 (LWP 2932))]#0  0xb7fe2430 in __kernel_vsyscall ()
(gdb) where # 查看切换后线程调用堆栈

#0  0xb7fe2430 in __kernel_vsyscall ()
#1  0xb7fc694d in pthread_join (threadid=3085405040, thread_return=0xbffff744) at pthread_join.c:89
#2  0x08048828 in main (argc=1, argv=0xbffff804) at main.c:36

10. 其他

调试子进程。

(gdb) set follow-fork-mode child

临时进入 Shell 执行命令,Exit 返回。

(gdb) shell

调试时直接调用函数。

(gdb) call test("abc")

使用 "--tui" 参数,可以在终端窗口上部显示一个源代码查看窗。

$ gdb --tui hello

查看命令帮助。

(gdb) help b

最后就是退出命令。

(gdb) q

和 Linux Base Shell 习惯一样,对于记不住的命令,可以在输入前几个字母后按 Tab 补全。

----------- 分隔线 ---------------

GDB 还有很多指令,功能也异常强大。不过对于熟悉了 VS 那种豪华 IDE 的人来说,这种命令行调试是种巨大的痛苦。尽管我个人建议多用 GDB,但也不反对用 GUI 调试器来加快调试进程。Nemiver 就不错,推荐一下。

作者:unbutun 发表于2011-6-28 21:46:00 原文链接
阅读:14 评论:0 查看评论
技巧:用 C 语言实现程序的多态性
王 广, 硕士研究生, 中国科学技术大学
 

简介: 使用面向对象的语言可以实现多态,并且在很大程度上降低了代码的复杂性。对于面向过程的 C 语言同样可以实现多态,本文将着重介绍 C 语言是如何实现多态的。

本文的标签:  c, c/cpp

 

标记本文!
 

发布日期: 2010 年 8 月 17 日 
级别: 初级 
访问情况 4212 次浏览 
建议: 23 (查看或添加评论)
 平均分 (共 41 个评分 )
 

前言:关于多态,关于 C

多态 (polymorphism) 一词最初来源于希腊语 polumorphos,含义是具有多种形式或形态的情形。在程序设计领域,一个广泛认可的定义是“一种将不同的特殊行为和单个泛化记号相关联的能力”。然而在人们的直观感觉中,多态的含义大约等同于“同一个方法对于不同类型的输入参数均能做出正确的处理过程,并给出人们所期望获得的结果”,也许这正体现了人们对于多态性所能达到的效果所寄予的期望:使程序能够做到越来越智能化,越来越易于使用,越来越能够使设计者透过形形色色的表象看到代码所要触及到的问题本质。

作为读者的你或许对于面向对象编程已有着精深的见解,或许对于多态的方便与神奇你也有了深入的认识。这时候你讶异的开始质疑了:“多态,那是面向对象编程才有的技术,C 语言是面向过程的啊!”而我想说的是,C 语言作为一种编程语言,也许并不是为了面向对象编程而设计,但这并不意味着它不能实现面向对象编程所能实现的功能,就比如说,多态性。

在本文中我们使用一个简单的单链表作为例子,展示 C 语言是如何体现多态性的。

回页首

结构体:不得不说的故事

许多从写 C 代码开始,逐渐走向 C++ 的程序员都知道,其实 C++ 里面的 class,其前身正是 C 语言中的 structure。很多基于 C 语言背景介绍 C++ 的书籍,在介绍到 class 这一章的时候都会向读者清晰地展示,一个 C 语言里的 structure 是怎样逐渐变成一个典型的 C++ class 的,甚至最后得出结论:“structure 就是一个所有成员都公有的类”,当然了,class 还是 class,不能简单的把它看做一个复杂化了的 structure 而已。

下面我们来看看在 C 语言中定义一个简单的存储整型数据的单链表节点是怎么做的,当然是用结构体。大部分人会像我一样,在 linkList.h 文件里定义:

 typedef struct Node* linkList; 
 struct Node                                       // 链表节点
 { 
 int data;                                 // 存储的整型数据
 linkList next;                            // 指向下一个链表节点
 }; 

链表有了,下面就是你想要实现的一些链表的功能,当然是定义成函数。我们只举几个常用功能:

 linkList initialLinklist();                               // 初始化链表
 link newLinkList (int data);                        // 建立新节点
 void insertFirst(linkList h,int data);              // 在已有链表的表头进行插入节点操作
 void linkListOutput(linkList h);                           // 输出链表中数据到控制台

这些都是再自然不过的 C 语言的编程过程,然后我们就可以在 linkList.c 文件中实现上述两个函数,继而在 main.c 中调用它们了。

然而上面我们定义的链表还只能对整型数据进行操作。如果下次你要用到一个存储字符串类型的链表,就只好把上面的过程重新来过。也许你觉得这个在原有代码基础上做略微修改的过程并不复杂,可是也许我们会不断的增加对于链表这个数据结构的操作,而需要用链表来存储的数据类型也越来越多,这些都意味着海量的代码和繁琐的后期维护工作。当你有了上百个存储不同数据类型的链表结构,每当你要增加一个操作,或者修改某个操作的传入参数,工作量会变大到像一场灾难。

但是我们可以改造上述代码,让它能够处理你所想要让它处理的任何数据类型:实行,字符型,乃至任何你自己定义的 structure 类型。

回页首

Void*:万能的指针“挂钩”

几乎所有讲授 C 语言课程的老师都会告诉你:“指针是整个 C 语言的精髓所在。”而你也一直敬畏着指针,又爱又恨地使用着它。许多教材都告诉你,int * 叫做指向整型的指针,而 char * 是指向字符型的指针,等等不一而足。然而这里有一个另类的指针家族成员—— void *。不要按照通常的命名方式叫它做指向 void 类型的指针,它的正式的名字叫做:可以指向任意类型的指针。你一定注意到了“任意类型”这四个字,没错,实现多态,我们靠的就是它。

下面来改造我们的链表代码,在 linkList.h 里,如下:

 typedef struct Node* linkList; 
 struct Node                                       // 链表节点
 { 
 void *data;                               // 存储的数据指针
 linkList next;                            // 指向下一个链表节点
 }; 

 linkList initialLinklist();                             // 初始化链表
 link newLinkList (void *data);                    // 建立新节点
 void insertFirst(linkList h, void *data);         // 在已有链表的表头进行插入节点操作
 void linkListOutput(linkList h);                         // 输出链表中数据到控制台

我们来看看现在这个链表和刚才那个只能存储整型数据的链表的区别。

当你把 Node 结构体里面的成员定义为一个整型数据,就好像把这个链表节点打造成了一个大小形状固定的盒子,你定义一个链表节点,程序进行编译的时候编译器就为你打造一个这样的盒子:装一个 int 类型的数据,然后装一个 linkList 类型的指针。如果你想强行在这个盒子里装别的东西,编译器会告诉你,对不起,盒子的大小形状并不合适。所以你必须为了装各种各样类型的数据打造出不同的生产盒子的流水线,想要装哪种类型数据的盒子,就开启对应的流水线来生产。

但是当你把结构体成员定义为 void *,一切都变得不同了。这时的链表节点不再像个大小形状固定的盒子,而更像一个挂钩,它可以挂上一个任意类型的数据。不管你需要存储什么类型的数据,你只要传递一个指针,把它存储到 Node 节点中去,就相当于把这个数据“挂”了上去,无论何时你都可以根据指针找到它。这时的链表仿佛变成了一排粘贴在墙上的衣帽钩,你可以挂一排大衣,可以挂一排帽子,可以挂一排围巾,甚至,你可以并排挂一件大衣一顶帽子一条围巾在墙上。void * 初露狰狞,多态离 C 语言并不遥远。

回页首

实现:你的多态你做主

当你真正开始着手做这个工作的时候,你会发现把数据放入链表中的操作和普通的存放 int 类型的链表的实现并没有什么大的区别,很方便。但是当你要把已经存进去的数据读取出来的时候,就有一点麻烦了。对于 void * 类型的指针,编译器只知道它里面存储了一个地址,但是关于这个地址里的数据类型,编译器是没有任何概念的。毕竟我们不能指望编译器什么都知道,什么都能替你做好,所以存进去的数据的类型,作为程序员的我们必须清楚的知道,并且在取出这个数据的时候,用这一类型的指针来对 void * 做强制类型转换。

为了方便的做到这一点,我采取的方法是在 Node 结构体中增加一个标识数据类型的域,并用一个枚举类型来存放这些数据类型。这时的 linkList.h 如下所示:

 #ifndef LINKLIST_H 
 #define LINKLIST_H 

 typedef struct Node* linkList; 

 enum dataType 
 { 
    INT, 
    DOUBLE, 
    CHAR, 
    STRING 
 }; 

 struct Node                                               // 链表节点
 { 
    void *data;                                       // 存储的数据指针
    int dataType;                                     // 存储数据类型
    linkList next;                                    // 指向下一个链表节点
 }; 

 linkList initialLinklist();                               // 初始化链表
 linkList newLinkList (void *data, int dataType);          // 建立新节点
 void insertFirst(linkList h, void *data, int dataType);   // 在已有链表的表头进行插入节点操作
 void linkListOutput(linkList h);                          // 输出链表中数据到控制台

 #endif 

初始化链表,代码如下:

 linkList initialLinklist() 
 { 
 linkList link = (linkList*)malloc(sizeof(*link)); 
    link->data = NULL; 
    link->dataType = -1; 
    link->next = NULL; 

    return link; 
 } 

建立新节点,代码如下:

 linkList newLinkList (void *data, int dataType) 
 { 
    linkList link = (linkList*)malloc(sizeof(*link)); 
    link->data = data; 
    link->dataType = dataType; 
    link->next = NULL; 

    return link; 
 } 

在已有链表的表头进行插入节点操作,代码如下:

 void insertFirst(linkList h, void *data, int dataType) 
 { 
    linkList l = newLinkList(data, dataType); 
    l->next = h->next; 
    h->next = l; 
 } 

输出链表中数据到控制台,代码如下:

 void linkListOutput(linkList h) 
 { 
    linkList p = h; 

    p = p->next; 
    while(p != NULL) 
    { 
      switch(p->dataType) 
    { 
       case 0: 
       { 
         printf("%4d", *(int*)(p->data)); 
         break; 
       } 
       case 1: 
       { 
         printf("%4f", *(double*)(p->data)); 
         break; 
       } 
       case 2: 
       { 
          printf("%4c", *(char*)(p->data)); 
          break; 
       } 
       case 3: 
       { 
          printf("%s ", (char*)(p->data)); 
          break; 
       } 
    } 
    p = p->next; 
  } 
    printf("/n"); 
 } 

回页首

小结

通过上面这个链表的小例子,大家可能已经看到了 C 语言的灵活性。这段代码虽然短并且功能简单,但是已经实现了多态性。这篇文章的本意并不在于想要告诉大家用 C 实现多态的方法,而多态的含义也无疑是更加广泛的。这篇文章的初衷其实是基于这样一点认识:面向对象是一种程序设计思想,而 C 语言则是一种编程语言。也许它并不是专门为了面向对象编程而设计,但是这绝不意味着它不能实现面向对象的程序设计。当然以上所展示的这几个操作,如果是用别的编程语言,可能只要寥寥几行就可以完成,但是 C 语言想告诉我们的是:也许我不擅长,但是并不意味着我做不到。


作者:unbutun 发表于2011-6-28 20:35:00 原文链接
阅读:17 评论:0 查看评论
判断变量是全数字
 

test_num.sh

 



 

作者:unbutun 发表于2011-6-26 11:46:00 原文链接
阅读:3 评论:0 查看评论
做一次git的观众
Git这个强大的版本管理系统,工作的时候默默注视着你的代码目录,所有的操作几乎都在.git目录中完成。今天我们来做一次git的观众,以便深入了解git的各个操作。 

首先,新建一个目录:git-monitor,进入目录后,用下面的命令初始化一个git仓库:

Bash代码  
{1}gt; git init --bare git-monitor.git  

然后,创建一个工作目录wp1,意思为working_copy_1,进入该目录,运行git init,以创建.git目录。 

进入.git目录,会发现下列文件和目录:

Bash代码  
HEAD        config      description hooks/       info/        objects/     refs/  

这些都是git的演员。我们当观众的,就从监控这些文件开始。但是演员分主角、配角,和跑龙套的,在这些文件中,config是配置文件,内容不会变的;hooks中的文件是一些回调程序的例子,删掉都没关系;description文件只为某些git的web应用提供描述信息。它们都是跑龙套的,剩下的文件和目录有:

Bash代码  
HEAD        info/        objects/       refs/  

在后续的操作中,还有两位要上场,分别是index文件和logs目录,至此,主要演员表为:

Bash代码  
HEAD        index       info/        objects/       refs/        logs/  

要用肉眼盯着它们看,实在不容易,于是我写了个ruby小程序(下载链接在最后),用于监控这些目录,一旦目录和文件有变化,就在控制台上向我们报告。我把这个程序放到.git目录下,并把它跑起来。接下来,好戏就开演了。 

回到wp1目录。新建一个文件file1.txt,然后看看监控程序,发现没有任何输出,说明git对刚才的操作没有响应。既然没反应,那我就接着操作,在file1.txt中加一行内容

File1.txt代码  
content added by wp1, 1st time  

再看看监控,还是没有反应。看来,只要我们不调用git命令,它就不会有反应。那我就调一个看看:

Bash代码  
{1}gt; git add .  

再看看监控,终于有反应了:

Bash代码  
Created file: index  (in dir: git-monitor/wp1/.git)   
Created file: c2/a04aa8cba9ba9a7a2fb8c9ecf74a3a0fc5e3fc  (in dir: git-monitor/wp1/.git/objects)  

git add这个命令,根据`man git-add`的解释,是把某个文件加入到index。这个index实际上就是工作目录下文件和目录状态的一个快照(stage),每一次git提交所产生的tree对象,就是依据index文件产生的(对index同志的详细采访,可以参考[url=http://progit.org/book/zh/ch9-2.html]这里[/url])。 

我们来看看产生的那个object到底是什么,根据git的规则,object的目录名加文件名,和起来是一个40字符的字符串,它是对文件内容进行SHA1 digest之后,用16进制编码得到的结果。此文件的内容是二进制的,要查看它,就要用下面的命令:

Bash代码  
{1}gt; git cat-file -t c2a04aa8cba9ba9a7a2fb8c9ecf74a3a0fc5e3fc   
blob   
{1}gt; git cat-file -p c2a04aa8cba9ba9a7a2fb8c9ecf74a3a0fc5e3fc   
content added by wp1, 1st time  

其中,-t这个参数是为了显示object类型,-p这个参数,是为了显示object的内容。显然,这个object就是刚才加进去的file1.txt,它是一个blob类型的对象,只存储文件内容和长度。 

接下来,我把这次添加的内容提交一下(git commit -m 'commit by wp1, 1st time'),再看看监控,又有输出了,这次的内容还真丰富啊:

Bash代码  
Changed file: index   
Created file: 16/71ae856c149673436da08f1ba026469c3a918d  (in dir: git-monitor/wp1/.git/objects)   
Created file: 30/c64c3a55b02f4c251565ef057d402f84751b56  (in dir: git-monitor/wp1/.git/objects)   
Created file: heads/master  (in dir: git-monitor/wp1/.git/refs)   
Created file: HEAD  (in dir: git-monitor/wp1/.git/logs)   
Created file: refs/heads/master  (in dir: git-monitor/wp1/.git/logs)  

首先,我们发现index文件被改变了。但是,经过我仔细比对两次的index文件的二进制字节码后发现,它的内容并没有改变,所以可能是它的修改时间发生了改变。对此我想说的是:请高人指点! 

再看后面新生成的两个文件,用我们上面的办法看看内容:

Bash代码  
{1}gt; git cat-file -p 1671ae856c149673436da08f1ba026469c3a918d   
tree 30c64c3a55b02f4c251565ef057d402f84751b56   
author Kevin Fu  1281230735 +0800  
committer Kevin Fu  1281230735 +0800  
  
commit by wp1, 1st time   
  
{1}gt; git cat-file -p 30c64c3a55b02f4c251565ef057d402f84751b56   
100644 blob c2a04aa8cba9ba9a7a2fb8c9ecf74a3a0fc5e3fc    file1.txt  

显然,第一个文件是个commit对象,第二个文件是个tree对象,从引用关系来看,是先生成的tree对象,再生成的commit对象。注意,这个commit对象没有parent引用。 

再看看后面生成的refs,用git show-refs可以查看所有refs的内容

Bash代码  
{1}gt; git show-refs   
1671ae856c149673436da08f1ba026469c3a918d refs/heads/master  

master当然指的是master分支,它的值指向刚才看到的commit对象 

最后就是两个log文件。log文件虽然只是供人查看,但在git中的地位非同一般。先看看其内容:

Bash代码  
$ cat logs/HEAD   
0000000000000000000000000000000000000000 1671ae856c149673436da08f1ba026469c3a918d Kevin Fu  1281230735 +0800 commit (initial): commit by wp1, 1st time   
  
$ cat logs/refs/heads/master   
0000000000000000000000000000000000000000 1671ae856c149673436da08f1ba026469c3a918d Kevin Fu  1281230735 +0800 commit (initial): commit by wp1, 1st time  

可以看到,这两个文件的内容目前是一样的。这个文件记录了什么呢?它记录的是工作目录的状态变化。其中,那一串0表示,一起是从零开始的,因为git在初始化工作目录时,并没有创建任何对象,要表示初始状态,只好用40个0来表示了。后面那串,就是指向刚才的本次的commit对象。这条记录解释出来,就是:在1281230735 +0800时刻,由Kevin Fu做了一次提交,工作目录从初始状态,转到commit 1671ae8对应的状态。 

这个时候,用过git的人可能会生疑问:用git log看到的输出不是这个样子的呀。说对了,git log的输出,并不是由这里的log文件产生的,我认为,它的内容是根据commit之间的关联关系,实时计算并显示出来的。而这里的logs,是git中的reflog,用git reflog命令可以查看:

Bash代码  
{1}gt; git reflog   
1671ae8 HEAD@{0}: commit (initial): commit by wp1, 1st time  

这个内容,就跟上面的对应上了吧。为什么说这个log文件很重要呢?因为在git中,objects其实都是死的,绝大多数情况下,一旦创建就不会被修改,也不会被删除;当版本控制的内容发生变化时,只有新的objects被创建出来,没有旧的objects被改变。那么一堆死东西,如何实现灵活的版本变化呢?第一个就是靠不断变化的版本指针,比如HEAD以及refs/heads/master文件,第二个,就是靠记录工作目录变化情况的日志文件。有了日志文件,你想查看谁就查看谁,想往哪个版本跳就往哪个版本跳,想合并谁就合并谁。许多git命令,都是基于这个思想而设计的。 


接下来,我再添加一个文件file2.txt,但不是在master branch中,而是新开一个branch: advanced,我们看看开分支的时候,监控有何变化:

Bash代码  
{1}gt; git checkout -b advanced   
(monitor outputs)   
Changed file: index   
Changed file: HEAD   
Created file: heads/advanced  (in dir: /Users/corntrace/git-monitor/wp1/.git/refs)   
Created file: refs/heads/advanced  (in dir: /Users/corntrace/git-monitor/wp1/.git/logs)   
Changed file: HEAD  (in dir: /Users/corntrace/git-monitor/wp1/.git/logs)  

除去那个index文件的变化,看看其他的文件。首先是HEAD文件,其内容变成了:refs/heads/advanced,说明HEAD已经移到了advanced分支上。再看看新产生的refs/heads/advanced文件:

Bash代码  
{1}gt; git show-ref   
1671ae856c149673436da08f1ba026469c3a918d refs/heads/advanced   
1671ae856c149673436da08f1ba026469c3a918d refs/heads/master  

可见目前它与master分支指向同一个commit。再来看看两个日志文件:

Bash代码  
$ cat logs/HEAD   
0000000000000000000000000000000000000000 1671ae856c149673436da08f1ba026469c3a918d Kevin Fu  1281230735 +0800 commit (initial): commit by wp1, 1st time   
1671ae856c149673436da08f1ba026469c3a918d 1671ae856c149673436da08f1ba026469c3a918d Kevin Fu  1281236136 +0800 checkout: moving from master to advanced  

它果然把我的一举一动都记录下来了。在第二条记录里,两个sha1值是一样的,说明没有提交,只有指针的创建或改变。 
再看看另一个log:

Bash代码 0000000000000000000000000000000000000000 1671ae856c149673436da08f1ba026469c3a918d Kevin Fu  1281236136 +0800 branch: Created from HEAD  

它说明了advanced分支是从零开始,转到commit 671ae8的。看来它一点都不含糊啊。 

现在,我增加文件file2.txt,并添加以下内容,但分两次提交:第一次提交前两行,第二次提交后两行

File2.txt代码
content added by wp1, 1st time   
additional content added by wp1, 1st time too   
  
# TODO: implement a feature   
# I plan to do ...  

显然,第一次提交将会产生3个objects:一个commit对象,一个file2.txt的blob对象,还有一个tree对象,另外,refs/heads/advanced会指向目前的这个commit对象,然后两个log文件(logs/HEAD和logs/refs/heads/advanced)会添加一些内容,我全部列在这里:

Bash代码
{1}gt; git show-ref   
35ba29ec8f31d5372b75d7be6b1ec7f03c0fb666 refs/heads/advanced   
1671ae856c149673436da08f1ba026469c3a918d refs/heads/master   
  
{1}gt; git cat-file -p 35ba29ec8f31d5372b75d7be6b1ec7f03c0fb666   
tree 9c1c4549a869ede4d5f85c93594c1c23c311122f   
parent 1671ae856c149673436da08f1ba026469c3a918d   
author Kevin Fu  1281251100 +0800  
committer Kevin Fu  1281251100 +0800  
  
commit by wp1 for file2.txt, 1st time   
  
{1}gt; git cat-file -p 9c1c4549a869ede4d5f85c93594c1c23c311122f    
100644 blob c2a04aa8cba9ba9a7a2fb8c9ecf74a3a0fc5e3fc    file1.txt   
100644 blob baa4a1630ce88a9198b5eda885884aadab795806    file2.txt   
  
{1}gt; git cat-file -p baa4a1630ce88a9198b5eda885884aadab795806    
content added by wp1, 1st time   
additional content added by wp1, 1st time   
  
{1}gt; cat logs/HEAD   
0000000000000000000000000000000000000000 1671ae856c149673436da08f1ba026469c3a918d Kevin Fu  1281230735 +0800 commit (initial): commit by wp1, 1st time   
1671ae856c149673436da08f1ba026469c3a918d 1671ae856c149673436da08f1ba026469c3a918d Kevin Fu  1281236136 +0800 checkout: moving from master to advanced   
1671ae856c149673436da08f1ba026469c3a918d 35ba29ec8f31d5372b75d7be6b1ec7f03c0fb666 Kevin Fu  1281251100 +0800 commit: commit by wp1 for file2.txt, 1st time   
  
{1}gt; cat logs/refs/heads/advanced   
0000000000000000000000000000000000000000 1671ae856c149673436da08f1ba026469c3a918d Kevin Fu  1281236136 +0800 branch: Created from HEAD   
1671ae856c149673436da08f1ba026469c3a918d 35ba29ec8f31d5372b75d7be6b1ec7f03c0fb666 Kevin Fu  1281251100 +0800 commit: commit by wp1 for file2.txt, 1st time  

可以看到,这次的commit对象中多了parent的引用,就是指向上一次的commit。 
接下来我提交第二部分的内容,其结果与刚才的分析相似,就不写了。提交之后,我转回master分支,将advanced分支中的内容合并进来,然后将master分支推送出去,我就可以下班了。 

我们看看转回master分支时,监控都有哪些输出:

Bash代码
Changed file: index   
Changed file: HEAD   
Changed file: HEAD  (in dir: /Users/corntrace/git-monitor/wp1/.git/logs)  

可见,除了那个index文件,HEAD文件的内容被该为:ref: refs/heads/master,logs/HEAD文件中添加了一行记录分支跳转的日志。 

接下来我运行git merge advanced。合并完成后,看看监控的输出:

Bash代码
Changed file: index   
Changed file: heads/master  (in dir: /Users/corntrace/git-monitor/wp1/.git/refs)   
Changed file: HEAD  (in dir: /Users/corntrace/git-monitor/wp1/.git/logs)   
Changed file: refs/heads/master  (in dir: /Users/corntrace/git-monitor/wp1/.git/logs)  

这里列出后三项的内容:

Bash代码
{1}gt; git show-ref   
8227ae64f1f651669c6445d4c37909c8443df209 refs/heads/advanced   
8227ae64f1f651669c6445d4c37909c8443df209 refs/heads/master   
  
{1}gt; cat logs/HEAD   
(猜都能猜出来,就省了吧)   
  
{1}gt; cat logs/refs/heads/master   
0000000000000000000000000000000000000000 1671ae856c149673436da08f1ba026469c3a918d Kevin Fu  1281230735 +0800 commit (initial): commit by wp1, 1st time   
1671ae856c149673436da08f1ba026469c3a918d 8227ae64f1f651669c6445d4c37909c8443df209 Kevin Fu  1281252267 +0800 merge advanced: Fast-forward  

从分支的指向可以看到,它指到advanced分支对应的commit上去了;从logs的内容可以看到,本次的操作称为merge advanced: Fast-forword。除此之外,并没有产生任何object,连commit都没有,这是因为在合并之后,git分析出当前的目录树结构与advanced分支中的目录树是一样的,所以只是简单的把master的指针指向advanced分支。 

OK,在下班之前,我还是要看看master分支中的代码,确保没有什么坏代码被交上去。但是,我的神,file2.txt中有一个TODO,这要是被老板看见了,还不要我晚上加班啊!我能把它删掉再提交吗?不能啊,老板要是往回看一个版本,不就找出来了!因此,当务之急就是,把master的版本指针退回去,让它指向原来的1671ae8这个版本。git reset这个命令可以帮到我。

Bash代码
{1}gt; git reset --hard 1671ae8   
(monitor outputs)   
Changed file: index   
Changed file: heads/master  (in dir: /Users/corntrace/git-monitor/wp1/.git/refs)   
Changed file: HEAD  (in dir: /Users/corntrace/git-monitor/wp1/.git/logs)   
Changed file: refs/heads/master  (in dir: /Users/corntrace/git-monitor/wp1/.git/logs)   
  
{1}gt; git show-ref   
8227ae64f1f651669c6445d4c37909c8443df209 refs/heads/advanced   
1671ae856c149673436da08f1ba026469c3a918d refs/heads/master   
  
{1}gt; cat logs/refs/heads/master   
0000000000000000000000000000000000000000 1671ae856c149673436da08f1ba026469c3a918d Kevin Fu  1281230735 +0800 commit (initial): commit by wp1, 1st time   
1671ae856c149673436da08f1ba026469c3a918d 8227ae64f1f651669c6445d4c37909c8443df209 Kevin Fu  1281252267 +0800 merge advanced: Fast-forward   
8227ae64f1f651669c6445d4c37909c8443df209 1671ae856c149673436da08f1ba026469c3a918d Kevin Fu  1281253859 +0800 1671ae8: updating HEAD  

可以看到,master的head是真的退回去了,reflog中的记录,稍候再说。现在只要把advanced中想要的那个提交弄过来,就万事大吉了。git cherry-pick这个命令这是用来做这个的。

Bash代码
{1}gt; git cherry-pick --ff 35ba29e   
(此时这里居然没有任何输出,git的作者们太高估用户了!来看看monitor outputs)   
Changed file: index   
Changed file: heads/master  (in dir: /Users/corntrace/git-monitor/wp1/.git/refs)   
Changed file: HEAD  (in dir: /Users/corntrace/git-monitor/wp1/.git/logs)   
Changed file: refs/heads/master  (in dir: /Users/corntrace/git-monitor/wp1/.git/logs)   
  
{1}gt; git show-ref   
8227ae64f1f651669c6445d4c37909c8443df209 refs/heads/advanced   
35ba29ec8f31d5372b75d7be6b1ec7f03c0fb666 refs/heads/master   
  
{1}gt; cat file2.txt   
content added by wp1, 1st time   
additional content added by wp1, 1st time  

看,TODO果然是没有了!在这里我用的--ff参数,是要告诉git,只需要做fast-forword就可以了,因为35ba29e这个commit的parent,正是先前的master的head(1671ae8)。 

OK,我可以把master分支推送到远程仓库里去了。在推之前,我需要把远程仓库加进来。这里我就用一个本地的仓库来代替远程的吧。

Bash代码
{1}gt; git remote add origin /path/to/git-monitor.git   
(这一步monitor无输出)   
  
{1}gt; git push origin master   
(monitor outputs)   
Created file: remotes/origin/master  (in dir: /Users/corntrace/git-monitor/wp1/.git/refs)   
Created file: refs/remotes/origin/master  (in dir: /Users/corntrace/git-monitor/wp1/.git/logs)   
  
{1}gt; git show-ref   
8227ae64f1f651669c6445d4c37909c8443df209 refs/heads/advanced   
35ba29ec8f31d5372b75d7be6b1ec7f03c0fb666 refs/heads/master   
35ba29ec8f31d5372b75d7be6b1ec7f03c0fb666 refs/remotes/origin/master   
  
{1}gt; cat logs/refs/remotes/origin/master   
0000000000000000000000000000000000000000 35ba29ec8f31d5372b75d7be6b1ec7f03c0fb666 Kevin Fu  1281255166 +0800 update by push  

可见,这一步的操作就是新建了一个remote master分支的ref,然后添加了一个logs文件。看来所有的事情都办妥了,那就下班吧。 

但是老板还没有下班,而是正准备做code review。他把代码checkout下来:

Bash代码
{1}gt; git co /path/to/git-monitor.git && cd git-monitor   
{1}gt; git reflog   
35ba29e HEAD@{0}: clone: from /Users/corntrace/git-monitor/git-monitor.git  

他的reflog文件中,完全没有我刚才操作的内容。看来,git是不会提交reflog到仓库中的。

作者:unbutun 发表于2011-6-24 19:52:00 原文链接
阅读:34 评论:0 查看评论
用CodeViz绘制静态函数调用关系图
首先要根据README里编译生成一个gcc的patch版本
用这个patch过的gcc就能生成.cdepn中间文件,
然后再用genfull和gengraph就能生成静态函数调用关系图了

genfull -g cdepn -o full.graph
gengraph -t --output-type png -f main

我现在遇到个程序只能生成个main,具体原因不明,这时候只能用替代的方法
genfull -g cobjdump了

作者:unbutun 发表于2011-6-24 0:05:00 原文链接
阅读:12 评论:0 查看评论
公司简介|广告服务|银行汇款帐号|联系方式|版权声明|法律顾问|问题报告
北京创新乐知信息技术有限公司 版权所有, 京 ICP 证 070598 号
世纪乐知(北京)网络技术有限公司 提供技术支持

Copyright © 1999-2011, CSDN.NET, All Rights Reserved

你可能感兴趣的:(Linux)