改进应用程序的性能是一项非常耗时耗力的工作,但是究竟程序中是哪些函数消耗掉了大部分执行时间,这通常都不是非常明显的。GNU 编译器工具包所提供了一种剖析工具 GNU profiler(gprof)。gprof 可以为 Linux平台上的程序精确分析性能瓶颈。gprof精确地给出函数被调用的时间和次数,给出函数调用关系。
gprof 用户手册网站 http://sourceware.org/binutils/docs-2.17/gprof/index.html
Gprof 是GNU gnu binutils工具之一,默认情况下linux系统当中都带有这个工具。
1. 可以显示“flat profile”,包括每个函数的调用次数,每个函数消耗的处理器时间,
2. 可以显示“Call graph”,包括函数的调用关系,每个函数调用花费了多少时间。
3. 可以显示“注释的源代码”--是程序源代码的一个复本,标记有程序中每行代码的执行次数。
通过在编译和链接程序的时候(使用 -pg 编译和链接选项),gcc 在你应用程序的每个函数中都加入了一个名为mcount ( or “_mcount” , or “__mcount” , 依赖于编译器或操作系统)的函数,也就是说你的应用程序里的每一个函数都会调用mcount, 而mcount 会在内存中保存一张函数调用图,并通过函数调用堆栈的形式查找子函数和父函数的地址。这张调用图也保存了所有与函数相关的调用时间,调用次数等等的所有信息。
1. 在编译和链接时 加上-pg选项。一般我们可以加在 makefile 中。
2. 执行编译的二进制程序。执行参数和方式同以前。
3. 在程序运行目录下 生成 gmon.out 文件。如果原来有gmon.out 文件,将会被重写。
4. 结束进程。这时 gmon.out 会再次被刷新。
5. 用 gprof 工具分析 gmon.out 文件。
l -b 不再输出统计图表中每个字段的详细描述。
l -p 只输出函数的调用图(Call graph的那部分信息)。
l -q 只输出函数的时间消耗列表。
l -e Name 不再输出函数Name 及其子函数的调用图(除非它们有未被限制的其它父函数)。可以给定多个 -e 标志。一个 -e 标志只能指定一个函数。
l -E Name 不再输出函数Name 及其子函数的调用图,此标志类似于 -e 标志,但它在总时间和百分比时间的计算中排除了由函数Name 及其子函数所用的时间。
l -f Name 输出函数Name 及其子函数的调用图。可以指定多个 -f 标志。一个 -f 标志只能指定一个函数。
l -F Name 输出函数Name 及其子函数的调用图,它类似于 -f 标志,但它在总时间和百分比时间计算中仅使用所打印的例程的时间。可以指定多个 -F 标志。一个 -F 标志只能指定一个函数。-F 标志覆盖 -E 标志。
l -z 显示使用次数为零的例程(按照调用计数和累积时间计算)。
一般用法: gprof –b 二进制程序 gmon.out >report.txt
Gprof 产生的信息解释:
%time |
Cumulative seconds |
Self Seconds |
Calls |
Self TS/call |
Total TS/call |
name |
该函数消耗时间占程序所有时间百分比 |
程序的累积执行时间 (只是包括gprof能够监控到的函数) |
该函数本身执行时间 (所有被调用次数的合共时间) |
函数被调用次数 |
函数平均执行时间 (不包括被调用时间) (函数的单次执行时间) |
函数平均执行时间 (包括被调用时间)
(函数的单次执行时间) |
函数名 |
Call Graph 的字段含义:
Index |
%time |
Self |
Children |
Called |
Name |
索引值 |
函数消耗时间占所有时间百分比 |
函数本身执行时间 |
执行子函数所用时间 |
被调用次数 |
函数名 |
注意:
程序的累积执行时间只是包括gprof能够监控到的函数。工作在内核态的函数和没有加-pg编译的第三方库函数是无法被gprof能够监控到的,(如sleep()等)
Gprof 的具体参数可以 通过 man gprof 查询。
对于代码剖析的支持是由编译器增加的,因此如果希望从共享库中获得剖析信息,就需要使用 -pg 来编译这些库。提供已经启用代码剖析支持而编译的 C 库版本(libc_p.a)。
如果需要分析系统函数(如libc库),可以用 –lc_p替换-lc。这样程序会链接libc_p.so或libc_p.a。这非常重要,因为只有这样才能监控到底层的c库函数的执行时间,(例如memcpy(),memset(),sprintf()等)。
gcc example1.c –pg -lc_p -o example1
注意要用ldd ./example | grep libc来查看程序链接的是libc.so还是libc_p.so
gprof 的最大缺陷:它只能分析应用程序在运行过程中所消耗掉的用户时间,无法得到程序内核空间的运行时间。通常来说,应用程序在运行时既要花费一些时间来运行用户代码,也要花费一些时间来运行 “系统代码”,例如内核系统调用sleep()。
有一个方法可以查看应用程序的运行时间组成,在 time 命令下面执行程序。这个命令会显示一个应用程序的实际运行时间、用户空间运行时间、内核空间运行时间。
如 time ./program
输出:
real 2m30.295s
user 0m0.000s
sys 0m0.004s
1. g++在编译和链接两个过程,都要使用-pg选项。
2. 只能使用静态连接libc库,否则在初始化*.so之前就调用profile代码会引起“segmentation fault”,解决办法是编译时加上-static-libgcc或-static。
3. 如果不用g++而使用ld直接链接程序,要加上链接文件/lib/gcrt0.o,如ld -o myprog /lib/gcrt0.o myprog.o utils.o -lc_p。也可能是gcrt1.o
4. 要监控到第三方库函数的执行时间,第三方库也必须是添加 –pg 选项编译的。
5. gprof只能分析应用程序所消耗掉的用户时间.
6. 程序不能以demon方式运行。否则采集不到时间。(可采集到调用次数)
7. 首先使用 time 来运行程序从而判断 gprof 是否能产生有用信息是个好方法。
8. 如果 gprof 不适合您的剖析需要,那么还有其他一些工具可以克服 gprof 部分缺陷,包括 OProfile 和 Sysprof。
9. gprof对于代码大部分是用户空间的CPU密集型的程序用处明显。对于大部分时间运行在内核空间或者由于外部因素(例如操作系统的 I/O 子系统过载)而运行得非常慢的程序难以进行优化。
10. gprof 不支持多线程应用,多线程下只能采集主线程性能数据。原因是gprof采用ITIMER_PROF信号,在多线程内只有主线程才能响应该信号。但是有一个简单的方法可以解决这一问题:http://sam.zoy.org/writings/programming/gprof.html
11. gprof只能在程序正常结束退出之后才能生成报告(gmon.out)。
a) 原因: gprof通过在atexit()里注册了一个函数来产生结果信息,任何非正常退出都不会执行atexit()的动作,所以不会产生gmon.out文件。
b) 程序可从main函数中正常退出,或者通过系统调用exit()函数退出。
gprof 不支持多线程应用,多线程下只能采集主线程性能数据。原因是gprof采用ITIMER_PROF信号,在多线程内只有主线程才能响应该信号。
采用什么方法才能够分析所有线程呢?关键是能够让各个线程都响应ITIMER_PROF信号。可以通过桩子函数来实现,重写pthread_create函数。
//////////////////// gprof-helper.c////////////////////////////
#define _GNU_SOURCE
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <pthread.h>
static void * wrapper_routine(void *);
/* Original pthread function */
static int (*pthread_create_orig)(pthread_t *__restrict,
__const pthread_attr_t *__restrict,
void *(*)(void *),
void *__restrict) = NULL;
/* Library initialization function */
void wooinit(void) __attribute__((constructor));
void wooinit(void)
{
pthread_create_orig = dlsym(RTLD_NEXT, "pthread_create");
fprintf(stderr, "pthreads: using profiling hooks for gprof\n");
if(pthread_create_orig == NULL)
{
char *error = dlerror();
if(error == NULL)
{
error = "pthread_create is NULL";
}
fprintf(stderr, "%s\n", error);
exit(EXIT_FAILURE);
}
}
/* Our data structure passed to the wrapper */
typedef struct wrapper_s
{
void * (*start_routine)(void *);
void * arg;
pthread_mutex_t lock;
pthread_cond_t wait;
struct itimerval itimer;
} wrapper_t;
/* The wrapper function in charge for setting the itimer value */
static void * wrapper_routine(void * data)
{
/* Put user data in thread-local variables */
void * (*start_routine)(void *) = ((wrapper_t*)data)->;start_routine;
void * arg = ((wrapper_t*)data)->;arg;
/* Set the profile timer value */
setitimer(ITIMER_PROF, &((wrapper_t*)data)->;itimer, NULL);
/* Tell the calling thread that we don't need its data anymore */
pthread_mutex_lock(&((wrapper_t*)data)->;lock);
pthread_cond_signal(&((wrapper_t*)data)->;wait);
pthread_mutex_unlock(&((wrapper_t*)data)->;lock);
/* Call the real function */
return start_routine(arg);
}
/* Our wrapper function for the real pthread_create() */
int pthread_create(pthread_t *__restrict thread,
__const pthread_attr_t *__restrict attr,
void * (*start_routine)(void *),
void *__restrict arg)
{
wrapper_t wrapper_data;
int i_return;
/* Initialize the wrapper structure */
wrapper_data.start_routine = start_routine;
wrapper_data.arg = arg;
getitimer(ITIMER_PROF, &wrapper_data.itimer);
pthread_cond_init(&wrapper_data.wait, NULL);
pthread_mutex_init(&wrapper_data.lock, NULL);
pthread_mutex_lock(&wrapper_data.lock);
/* The real pthread_create call */
i_return = pthread_create_orig(thread,
attr,
&wrapper_routine,
&wrapper_data);
/* If the thread was successfully spawned, wait for the data
* to be released */
if(i_return == 0)
{
pthread_cond_wait(&wrapper_data.wait, &wrapper_data.lock);
}
pthread_mutex_unlock(&wrapper_data.lock);
pthread_mutex_destroy(&wrapper_data.lock);
pthread_cond_destroy(&wrapper_data.wait);
return i_return;
}
///////////////////
然后编译成动态库 gcc -shared -fPIC gprof-helper.c -o gprof-helper.so -lpthread -ldl
使用例子:
/////////////////////a.c/////////////////////////////
#include <stdio.h>;
#include <stdlib.h>;
#include <unistd.h>;
#include <pthread.h>;
#include <string.h>;
void fun1();
void fun2();
void* fun(void * argv);
int main()
{
int i =0;
int id;
pthread_t thread[100];
for(i =0 ;i< 100; i++)
{
id = pthread_create(&thread[i], NULL, fun, NULL);
printf("thread =%d\n",i);
}
printf("dsfsd\n");
return 0;
}
void* fun(void * argv)
{
fun1();
fun2();
return NULL;
}
void fun1()
{
int i = 0;
while(i<100)
{
i++;
printf("fun1\n");
}
}
void fun2()
{
int i = 0;
int b;
while(i<50)
{
i++;
printf("fun2\n");
//b+=i;
}
}
///////////////
gcc -pg a.c gprof-helper.so
运行程序:
./a.out
分析gmon.out:
gprof -b a.out gmon.out
一. 概要
1. Gprof 是在运行中收集程序的统计信息。
程序的运行方式会严重影响统计的信息结果。因为不同的执行路径下,程序的行为方式会有很大的差别。
2. Gprof 使用步骤
(1) 编译链接程序的时候,使能Gprof
(2) 执行程序生成profile data file
(3) 使用Gprof分析profile data
3. 输出格式
(1) flat profile
每一个函数花费了多少时间,每一个函数被调用了多少次
(2) call graph
对每一个函数来说,哪个函数调用了它,多少次;它调用了哪些个函数,多少次
这个函数花费了多少时间,它调用的函数花费了多少时间
(3) annotated source
生成一份源代码的拷贝,标注出每一个block被执行了多少次
二. 为 Gprof 编译程序
1. 给GNU Tools 传递PG选项
gcc -g -pg .............
编译器和连接器都需要加上-pg 选项
2. 执行程序
程序执行结束后,会生成gmon.out 统计文件,这个就是profiler data file。
注意:只有在程序正常终止的时候,才会生成这个文件。也就是说,程序必须是从exit或者return终止的。
3. 分析生成结果
gprof [Options] <可执行文件> <profiler data file>
三. Gprof 的输出格式
G平日哦方可生成四种格式的统计结果,其中的line-by-line的输出,已经转移到gconv工具上。
所以目前的Gprof主要是三种格式的输出。
1. Flat Profile:
2. Call Graph
granularity: each sample hit covers 2 byte(s) for 20.00% of 0.05 seconds
index % time self children called name
<spontaneous>
[1] 100.0 0.00 0.05 start [1]
0.00 0.05 1/1 main [2]
0.00 0.00 1/2 on_exit [28]
0.00 0.00 1/1 exit [59]
-----------------------------------------------
0.00 0.05 1/1 start [1]
[2] 100.0 0.00 0.05 1 main [2]
0.00 0.05 1/1 report [3]
-----------------------------------------------
0.00 0.05 1/1 main [2]
[3] 100.0 0.00 0.05 1 report [3]
0.00 0.03 8/8 timelocal [6]
0.00 0.01 1/1 print [9]
0.00 0.01 9/9 fgets [12]
0.00 0.00 12/34 strncmp <cycle 1> [40]
0.00 0.00 8/8 lookup [20]
0.00 0.00 1/1 fopen [21]
0.00 0.00 8/8 chewtime [24]
0.00 0.00 8/16 skipspace [44]
-----------------------------------------------
[4] 59.8 0.01 0.02 8+472 <cycle 2 as a whole> [4]
0.01 0.02 244+260 offtime <cycle 2> [7]
0.00 0.00 236+1 tzset <cycle 2> [26]
-----------------------------------------------
虚线分割的每一个部分叫做一个entity,用来描述一个函数或者一个cycle。
其中每一个以 [数字] 开始的行叫做 primary line,这个entity主要就是描述它的。
在 primary line 之前的行显示的是调用这个函数的函数,也就是caller。在它之后
的是这个函数调用的函数,也就是subroutine。
2.1 Primary Line
index % time self children called name
...
[3] 100.0 0.00 0.05 1 report [3]
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2.2 Lines for a function's caller
index % time self children called name
...
0.00 0.05 1/1 main [2]
[3] 100.0 0.00 0.05 1 report [3]
如果caller不能确定,就会出现<spontaneous>作为caller。
GCC 命令行详解 -L 指定库的路径 -l 指定需连接的库名
http://www.ezdoum.com/stories.php?story=07/10/10/5189338
1. 重新编译glib
CFLAGS="-pg -g" ./autogen.sh --disable-gtk-doc --enable-debug=minimum \ --prefix=/your install path --disable-shared
make; make install
2. 写个测试代码
#define REPEATS 100000 #include <glib.h> #include <glib-object.h> /*************************/ #define TYPE_TEST_OBJECT (test_object_get_type ()) #define TEST_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ TYPE_TEST_OBJECT, TestObject)) #define IS_TEST_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \ TYPE_TEST_OBJECT)) typedef struct _TestObject TestObject; typedef struct _TestObjectClass TestObjectClass; struct _TestObject { GObject parent_instance; gint foo; gchar *bar; }; struct _TestObjectClass { GObjectClass parent_class; }; GType test_object_get_type (void) G_GNUC_CONST; TestObject *test_object_new (void); enum { PROP_0, PROP_FOO, PROP_BAR }; static void test_object_class_init (TestObjectClass *klass); static void test_object_init (TestObject *object); static void test_object_finalize (GObject *object); static void test_object_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void test_object_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static GObjectClass *parent_class = NULL; GType test_object_get_type (void) { static GType test_object_type = 0; if (!test_object_type) { static const GTypeInfo test_object_info = { sizeof (TestObjectClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) test_object_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof (TestObject), 0, /* n_preallocs */ (GInstanceInitFunc) test_object_init, }; test_object_type = g_type_register_static (G_TYPE_OBJECT, "TestObject", &test_object_info, 0); } return test_object_type; } static void test_object_class_init (TestObjectClass *klass) { GObjectClass *object_class; GParamSpec *param_spec; object_class = G_OBJECT_CLASS (klass); parent_class = g_type_class_peek_parent (klass); object_class->set_property = test_object_set_property; object_class->get_property = test_object_get_property; param_spec = g_param_spec_string ("bar", NULL, NULL, NULL, G_PARAM_READABLE | G_PARAM_WRITABLE #ifdef MAKE_IT_SLOW_2 | G_PARAM_CONSTRUCT_ONLY #endif ); g_object_class_install_property (object_class, PROP_BAR, param_spec); param_spec = g_param_spec_int ("foo", NULL, NULL, G_MININT, G_MAXINT, 0, G_PARAM_READABLE | G_PARAM_WRITABLE #ifdef MAKE_IT_SLOW_2 | G_PARAM_CONSTRUCT_ONLY #endif ); g_object_class_install_property (object_class, PROP_FOO, param_spec); object_class->finalize = test_object_finalize; } static void test_object_init (TestObject *object) { object->bar = NULL; object->foo = 0; } static void test_object_finalize (GObject *object) { TestObject *obj; obj = (TestObject *) (object); g_free (obj->bar); if (G_OBJECT_CLASS (parent_class)->finalize) G_OBJECT_CLASS (parent_class)->finalize (object); } static void test_object_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { TestObject *test_object; test_object = TEST_OBJECT (object); switch (property_id) { case PROP_FOO: test_object->foo = g_value_get_int (value); break; case PROP_BAR: g_free (test_object->bar); test_object->bar = g_value_dup_string (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void test_object_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { TestObject *test_object; test_object = TEST_OBJECT (object); switch (property_id) { case PROP_BAR: g_value_set_string (value, test_object->bar); break; case PROP_FOO: g_value_set_int (value, test_object->foo); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } TestObject * test_object_new (void) { #ifdef MAKE_IT_SLOW_1 return TEST_OBJECT (g_object_new (test_object_get_type (), "foo", 5, "bar", "blah", NULL)); #else TestObject *obj = TEST_OBJECT (g_object_new (test_object_get_type (), NULL)); obj->foo = 5; obj->bar = g_strdup ("blah"); return obj; #endif } /*************************/ int main (int argc, char *argv[]) { GTimeVal start, end; int i; TestObject *obj[REPEATS]; g_type_init_with_debug_flags (G_TYPE_DEBUG_NONE); test_object_new (); g_get_current_time (&start); for (i = 0; i < REPEATS; i++) obj[i] = test_object_new (); g_get_current_time (&end); if (end.tv_usec - start.tv_usec < 0) end.tv_sec--, end.tv_usec += 1000000; g_print ("loop %d, %ld.%06ld sec\n", REPEATS, (end.tv_sec - start.tv_sec), (end.tv_usec - start.tv_usec)); return 0; }
3. 编译测试代码
Then try: gcc -Wall -pg -g gobject-test.c -o gobject-test \ `pkg-config --cflags --libs gobject-2.0` vs. gcc -DMAKE_IT_SLOW_1 -Wall -pg -g gobject-test.c -o gobject-test \ `pkg-config --cflags gobject-2.0` `pkg-config --libs gobject-2.0` or gcc -DMAKE_IT_SLOW_2 -Wall -pg -g gobject-test.c -o gobject-test \ `pkg-config --cflags gobject-2.0` `pkg-config --libs gobject-2.0`
4. 用gprof看看:
gprof -b ./gobject-test Flat profile: Each sample counts as 0.01 seconds. no time accumulated % cumulative self self total time seconds seconds calls Ts/call Ts/call name 0.00 0.00 0.00 200002 0.00 0.00 test_object_get_type 0.00 0.00 0.00 100001 0.00 0.00 test_object_new Call graph granularity: each sample hit covers 2 byte(s) no time propagated index % time self children called name 0.00 0.00 200002/200002 test_object_new [2] [1] 0.0 0.00 0.00 200002 test_object_get_type [1] ----------------------------------------------- 0.00 0.00 100001/100001 main [8] [2] 0.0 0.00 0.00 100001 test_object_new [2] 0.00 0.00 200002/200002 test_object_get_type [1] ----------------------------------------------- Index by function name [1] test_object_get_type [2] test_object_new
build again:
gcc -DMAKE_IT_SLOW_1 -Wall -pg -g gobject-test.c -o gobject-test `pkg-config --cflags --libs gobject-2.0`
gprof -b ./gobject-test Flat profile: Each sample counts as 0.01 seconds. % cumulative self self total time seconds seconds calls ns/call ns/call name 50.12 0.01 0.01 400004 12.53 12.53 test_object_get_type 50.12 0.01 0.01 frame_dummy 0.00 0.01 0.00 100001 0.00 25.06 test_object_new Call graph granularity: each sample hit covers 2 byte(s) for 99.77% of 0.01 seconds index % time self children called name 0.00 0.00 200002/400004 test_object_set_property [5] 0.00 0.00 200002/400004 test_object_new [3] [1] 50.0 0.01 0.00 400004 test_object_get_type [1] ----------------------------------------------- <spontaneous> [2] 50.0 0.01 0.00 frame_dummy [2] ----------------------------------------------- 0.00 0.00 100001/100001 main [4] [3] 25.0 0.00 0.00 100001 test_object_new [3] 0.00 0.00 200002/400004 test_object_get_type [1] ----------------------------------------------- <spontaneous> [4] 25.0 0.00 0.00 main [4] 0.00 0.00 100001/100001 test_object_new [3] ----------------------------------------------- <spontaneous> [5] 25.0 0.00 0.00 test_object_set_property [5] 0.00 0.00 200002/400004 test_object_get_type [1] ----------------------------------------------- Index by function name [2] frame_dummy [1] test_object_get_type [3] test_object_new
build again:
gcc -DMAKE_IT_SLOW_2 -Wall -pg -g gobject-test.c -o gobject-test `pkg-config --cflags --libs gobject-2.0`
gprof -b ./gobject-test Flat profile: Each sample counts as 0.01 seconds. no time accumulated % cumulative self self total time seconds seconds calls Ts/call Ts/call name 0.00 0.00 0.00 400004 0.00 0.00 test_object_get_type 0.00 0.00 0.00 100001 0.00 0.00 test_object_new Call graph granularity: each sample hit covers 2 byte(s) no time propagated index % time self children called name 0.00 0.00 200002/400004 test_object_set_property [14] 0.00 0.00 200002/400004 test_object_new [2] [1] 0.0 0.00 0.00 400004 test_object_get_type [1] ----------------------------------------------- 0.00 0.00 100001/100001 main [8] [2] 0.0 0.00 0.00 100001 test_object_new [2] 0.00 0.00 200002/400004 test_object_get_type [1] ----------------------------------------------- Index by function name [1] test_object_get_type [2] test_object_new
我们从一个实际的例子看看:
1. New一个signal
在类初始化时,先指定了成员method:
klass->show = gtk_widget_real_show;
然后定义信号:
widget_signals[SHOW] = g_signal_new (I_("show"), G_TYPE_FROM_CLASS (gobject_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GtkWidgetClass, show), NULL, NULL, _gtk_marshal_VOID__VOID, G_TYPE_NONE, 0);
这里注意:G_STRUCT_OFFSET( GtkWidgetClass, show)
这个是靠地址偏移,找到GtkWidgetClass的成员函数指针的地址
2. 往下看:
guint g_signal_new (const gchar *signal_name, GType itype, GSignalFlags signal_flags, guint class_offset, GSignalAccumulator accumulator, gpointer accu_data, GSignalCMarshaller c_marshaller, GType return_type, guint n_params, ...) { va_list args; guint signal_id; g_return_val_if_fail (signal_name != NULL, 0); va_start (args, n_params); signal_id = g_signal_new_valist (signal_name, itype, signal_flags, class_offset ? g_signal_type_cclosure_new (itype, class_offset) : NULL, accumulator, accu_data, c_marshaller, return_type, n_params, args); va_end (args);
一般情况下,class_offset不是0, 所以会调用:g_signal_type_cclosure_new (itype, class_offset)
GClosure* g_signal_type_cclosure_new (GType itype, guint struct_offset) { GClosure *closure; g_return_val_if_fail (G_TYPE_IS_CLASSED (itype) || G_TYPE_IS_INTERFACE (itype), NULL); g_return_val_if_fail (struct_offset >= sizeof (GTypeClass), NULL); closure = g_closure_new_simple (sizeof (GClosure), (gpointer) itype); if (G_TYPE_IS_INTERFACE (itype)) g_closure_set_meta_marshal (closure, GUINT_TO_POINTER (struct_offset), g_type_iface_meta_marshal); else g_closure_set_meta_marshal (closure, GUINT_TO_POINTER (struct_offset), g_type_class_meta_marshal); return closure; }
GUINT_TO_POINTER (struct_offset), 这个就是函数指针:指的就是 GtkWidgetClass的Method:(*show)
这个(*show)作为回调函数的。
往下看:
void g_closure_set_meta_marshal (GClosure *closure, gpointer marshal_data, GClosureMarshal meta_marshal) { GClosureNotifyData *notifiers; g_return_if_fail (closure != NULL); g_return_if_fail (meta_marshal != NULL); g_return_if_fail (closure->is_invalid == FALSE); g_return_if_fail (closure->in_marshal == FALSE); g_return_if_fail (closure->meta_marshal == 0); notifiers = closure->notifiers; closure->notifiers = g_renew (GClosureNotifyData, NULL, CLOSURE_N_NOTIFIERS (closure) + 1); if (notifiers) { /* usually the meta marshal will be setup right after creation, so the * g_memmove() should be rare-case scenario */ g_memmove (closure->notifiers + 1, notifiers, CLOSURE_N_NOTIFIERS (closure) * sizeof (notifiers[0])); g_free (notifiers); } closure->notifiers[0].data = marshal_data; closure->notifiers[0].notify = (GClosureNotify) meta_marshal; SET (closure, meta_marshal, 1); }
这里的marshal_data, 就是上面的:GUINT_TO_POINTER (struct_offset), 就是用户指定的callback了。
同时,这个callback作为最后一个参数,传入:g_type_iface_meta_marshal
static void g_type_class_meta_marshal (GClosure *closure, GValue /*out*/ *return_value, guint n_param_values, const GValue *param_values, gpointer invocation_hint, gpointer marshal_data) { GTypeClass *class; gpointer callback; /* GType itype = (GType) closure->data; */ guint offset = GPOINTER_TO_UINT (marshal_data); class = G_TYPE_INSTANCE_GET_CLASS (g_value_peek_pointer (param_values + 0), itype, GTypeClass); callback = G_STRUCT_MEMBER (gpointer, class, offset); if (callback) closure->marshal (closure, return_value, n_param_values, param_values, invocation_hint, callback); }
在这个函数里面,首先提取callback.
如果有callback的化,后面会把callback做为最后一个参数,传入到marshal里面;
比如:_gtk_marshal_VOID__VOID == g_cclosure_marshal_VOID__VOID
void g_cclosure_marshal_VOID__VOID (GClosure *closure, GValue *return_value G_GNUC_UNUSED, guint n_param_values, const GValue *param_values, gpointer invocation_hint G_GNUC_UNUSED, gpointer marshal_data) { typedef void (*GMarshalFunc_VOID__VOID) (gpointer data1, gpointer data2); register GMarshalFunc_VOID__VOID callback; register GCClosure *cc = (GCClosure*) closure; register gpointer data1, data2; g_return_if_fail (n_param_values == 1); if (G_CCLOSURE_SWAP_DATA (closure)) { data1 = closure->data; data2 = g_value_peek_pointer (param_values + 0); } else { data1 = g_value_peek_pointer (param_values + 0); data2 = closure->data; } callback = (GMarshalFunc_VOID__VOID) (marshal_data ? marshal_data : cc->callback); callback (data1, data2); }
最后真正调用的这是前面指定的callback: klass->show = gtk_widget_real_show;
上边把user callback同closure绑定之后,g_signal_new_valist会调用g_signal_newv
guint g_signal_newv (const gchar *signal_name, GType itype, GSignalFlags signal_flags, GClosure *class_closure, GSignalAccumulator accumulator, gpointer accu_data, GSignalCMarshaller c_marshaller, GType return_type, guint n_params, GType *param_types) { gchar *name; guint signal_id, i; SignalNode *node; g_return_val_if_fail (signal_name != NULL, 0); g_return_val_if_fail (G_TYPE_IS_INSTANTIATABLE (itype) || G_TYPE_IS_INTERFACE (itype), 0); if (n_params) g_return_val_if_fail (param_types != NULL, 0); g_return_val_if_fail ((return_type & G_SIGNAL_TYPE_STATIC_SCOPE) == 0, 0); if (return_type == (G_TYPE_NONE & ~G_SIGNAL_TYPE_STATIC_SCOPE)) g_return_val_if_fail (accumulator == NULL, 0); if (!accumulator) g_return_val_if_fail (accu_data == NULL, 0); name = g_strdup (signal_name); g_strdelimit (name, G_STR_DELIMITERS ":^", '_'); /* FIXME do character checks like for types */ SIGNAL_LOCK (); signal_id = signal_id_lookup (g_quark_try_string (name), itype); node = LOOKUP_SIGNAL_NODE (signal_id); /* setup permanent portion of signal node */ if (!node) { SignalKey key; signal_id = g_n_signal_nodes++; node = g_new (SignalNode, 1); node->signal_id = signal_id; g_signal_nodes = g_renew (SignalNode*, g_signal_nodes, g_n_signal_nodes); g_signal_nodes[signal_id] = node; node->itype = itype; node->name = name; key.itype = itype; key.quark = g_quark_from_string (node->name); key.signal_id = signal_id; g_signal_key_bsa = g_bsearch_array_insert (g_signal_key_bsa, &g_signal_key_bconfig, &key); g_strdelimit (name, "_", '-'); node->name = g_intern_string (name); key.quark = g_quark_from_string (name); g_signal_key_bsa = g_bsearch_array_insert (g_signal_key_bsa, &g_signal_key_bconfig, &key); TRACE(GOBJECT_SIGNAL_NEW(signal_id, name, itype)); } node->destroyed = FALSE; node->test_class_offset = 0; /* setup reinitializable portion */ node->flags = signal_flags & G_SIGNAL_FLAGS_MASK; node->n_params = n_params; node->param_types = g_memdup (param_types, sizeof (GType) * n_params); node->return_type = return_type; node->class_closure_bsa = NULL; if (accumulator) { node->accumulator = g_new (SignalAccumulator, 1); node->accumulator->func = accumulator; node->accumulator->data = accu_data; } else node->accumulator = NULL; node->c_marshaller = c_marshaller; node->emission_hooks = NULL; if (class_closure) signal_add_class_closure (node, 0, class_closure); else if (G_TYPE_IS_INSTANTIATABLE (itype) && return_type == G_TYPE_NONE) { /* optimize NOP emissions */ node->test_class_offset = TEST_CLASS_MAGIC; } SIGNAL_UNLOCK (); g_free (name); return signal_id; }
上面这个函数完成:
1. 如果是新建的Signal, 则把它放入SignalNode数组中:g_signal_nodes[signal_id]
2. 并且放入用于查询的二分数组中:g_signal_key_bsa
3. 因为user指定了自己的回调,所以signal_add_class_closure()把class closure也放入
到相关的二分查找数组中:node->class_closure_bsa
4. 指定c_marshal, 这是:_gtk_marshal_VOID__VOID
1. Signal Node生成
2. callback指定
3. c Marshal指定
gsignal.c提供了3个相关的API:
gulong g_signal_connect_closure_by_id (gpointer instance, guint signal_id, GQuark detail, GClosure *closure, gboolean after); gulong g_signal_connect_closure (gpointer instance, const gchar *detailed_signal, GClosure *closure, gboolean after); gulong g_signal_connect_data (gpointer instance, const gchar *detailed_signal, GCallback c_handler, gpointer data, GClosureNotify destroy_data, GConnectFlags connect_flags);
用的最多的是g_signal_connect_data, 直接用它的也有,不过大多是用经过包装后的:
gtype.h:
#define g_signal_connect(instance, detailed_signal, c_handler, data) \ g_signal_connect_data ((instance), (detailed_signal), (c_handler), (data), NULL, (GConnectFlags) 0)
#define g_signal_connect_after(instance, detailed_signal, c_handler, data) \ g_signal_connect_data ((instance), (detailed_signal), (c_handler), (data), NULL, G_CONNECT_AFTER)
#define g_signal_connect_swapped(instance, detailed_signal, c_handler, data) \ g_signal_connect_data ((instance), (detailed_signal), (c_handler), (data), NULL, G_CONNECT_SWAPPED)
以及:
gobject.c:
gulong g_signal_connect_object (gpointer instance, const gchar *detailed_signal, GCallback c_handler, gpointer gobject, GConnectFlags connect_flags)
这个函数其实是封装:g_signal_connect_closure/g_signal_connect_data
gulong g_signal_connect_object (gpointer instance, const gchar *detailed_signal, GCallback c_handler, gpointer gobject, GConnectFlags connect_flags) { g_return_val_if_fail (G_TYPE_CHECK_INSTANCE (instance), 0); g_return_val_if_fail (detailed_signal != NULL, 0); g_return_val_if_fail (c_handler != NULL, 0); if (gobject) { GClosure *closure; g_return_val_if_fail (G_IS_OBJECT (gobject), 0); closure = ((connect_flags & G_CONNECT_SWAPPED) ? g_cclosure_new_object_swap : g_cclosure_new_object) (c_handler, gobject); return g_signal_connect_closure (instance, detailed_signal, closure, connect_flags & G_CONNECT_AFTER); } else return g_signal_connect_data (instance, detailed_signal, c_handler, NULL, NULL, connect_flags); }
void gtk_widget_show (GtkWidget *widget) { g_return_if_fail (GTK_IS_WIDGET (widget)); if (!gtk_widget_get_visible (widget)) { g_object_ref (widget); gtk_widget_push_verify_invariants (widget); if (!gtk_widget_is_toplevel (widget)) gtk_widget_queue_resize (widget); /* see comment in set_parent() for why this should and can be * conditional */ if (widget->priv->need_compute_expand || widget->priv->computed_hexpand || widget->priv->computed_vexpand) { if (widget->priv->parent != NULL) gtk_widget_queue_compute_expand (widget->priv->parent); } g_signal_emit (widget, widget_signals[SHOW], 0); g_object_notify (G_OBJECT (widget), "visible"); gtk_widget_pop_verify_invariants (widget); g_object_unref (widget); } }
void g_signal_emit (gpointer instance, guint signal_id, GQuark detail, ...) { va_list var_args; va_start (var_args, detail); g_signal_emit_valist (instance, signal_id, detail, var_args); va_end (var_args); }
static gboolean signal_emit_unlocked_R (SignalNode *node, GQuark detail, gpointer instance, GValue *emission_return, const GValue *instance_and_params) { SignalAccumulator *accumulator; Emission emission; GClosure *class_closure; HandlerList *hlist; Handler *handler_list = NULL; GValue *return_accu, accu = { 0, }; guint signal_id; gulong max_sequential_handler_number; gboolean return_value_altered = FALSE; emission.instance = instance; emission.ihint.signal_id = node->signal_id; emission.ihint.detail = detail; emission.ihint.run_type = 0; emission.state = 0; emission.chain_type = G_TYPE_NONE; emission_push ((node->flags & G_SIGNAL_NO_RECURSE) ? &g_restart_emissions : &g_recursive_emissions, &emission); class_closure = signal_lookup_closure (node, instance); EMIT_RESTART: if (handler_list) handler_unref_R (signal_id, instance, handler_list); max_sequential_handler_number = g_handler_sequential_number; hlist = handler_list_lookup (signal_id, instance); handler_list = hlist ? hlist->handlers : NULL; if (handler_list) handler_ref (handler_list); emission.ihint.run_type = G_SIGNAL_RUN_FIRST; if ((node->flags & G_SIGNAL_RUN_FIRST) && class_closure) { emission.state = EMISSION_RUN; emission.chain_type = G_TYPE_FROM_INSTANCE (instance); SIGNAL_UNLOCK (); g_closure_invoke (class_closure, return_accu, node->n_params + 1, instance_and_params, &emission.ihint); if (!accumulate (&emission.ihint, emission_return, &accu, accumulator) && emission.state == EMISSION_RUN) emission.state = EMISSION_STOP; SIGNAL_LOCK (); emission.chain_type = G_TYPE_NONE; return_value_altered = TRUE; if (emission.state == EMISSION_STOP) goto EMIT_CLEANUP; else if (emission.state == EMISSION_RESTART) goto EMIT_RESTART; }
上面做如下的动作:
1)做一个emmission, 收集好signal的相关信息,并且压入list中,临时保存信息
2)找到和这个signal相关联的closure (callback)
3) 根据signal id, 找到hander list
4) 如果有closure, 则调用g_closure_invoke(), 启动callback流程
if ((node->flags & G_SIGNAL_RUN_FIRST) && class_closure) { emission.state = EMISSION_RUN; emission.chain_type = G_TYPE_FROM_INSTANCE (instance); SIGNAL_UNLOCK (); g_closure_invoke (class_closure, return_accu, node->n_params + 1, instance_and_params, &emission.ihint);
4.1) 调用closure_invoke_notifier() //保护参数
4.2) 执行marshal(), 其中在marshal中调用 user callback
因为参数传入时,用GValue形式传入,在marshal中:
(1)需要把参数由Gvalue --> C 参数形式
(2)调用user callback
(3)把返回的值再包装成GValue形式带回
5. 如果通过g_signal_connect_xxx() 注册了自己的回调,会调用handler_list
if (handler_list) { Handler *handler = handler_list; emission.state = EMISSION_RUN; handler_ref (handler); do { Handler *tmp; if (handler->after) { handler_unref_R (signal_id, instance, handler_list); handler_list = handler; break; } else if (!handler->block_count && (!handler->detail || handler->detail == detail) && handler->sequential_number < max_sequential_handler_number) { SIGNAL_UNLOCK (); g_closure_invoke (handler->closure, return_accu, node->n_params + 1, instance_and_params, &emission.ihint);
在 2.6 内核中,随处可以见到 likely() 和 unlikely() 的身影,那么为什么要用它们?它们之间有什么区别?
首先要明确:
if(likely(value)) 等价于 if(value)
if(unlikely(value)) 也等价于 if(value)
也就是说 likely() 和 unlikely() 从阅读和理解代码的角度来看,是一样的!!!
这两个宏在内核中定义如下:
__builtin_expect() 是 GCC (version >= 2.96)提供给程序员使用的,目的是将“分支转移”的信息提供给编译器,这样编译器可以对代码进行优化,以减少指令跳转带来的性能下降。
__builtin_expect((x),1) 表示 x 的值为真的可能性更大;
__builtin_expect((x),0) 表示 x 的值为假的可能性更大。
也就是说,使用 likely() ,执行 if 后面的语句 的机会更大,使用unlikely(),执行else 后面的语句的机会更大。
例如下面这段代码,作者就认为 prev 不等于 next 的可能性更大,
通过这种方式,编译器在编译过程中,会将可能性更大的代码紧跟着起面的代码,从而减少指令跳转带来的性能上的下降。
下面以两个例子来加深这种理解:
第一个例子: example1.c
在这个例子中,我们认为 x 为0的可能性更大
编译以后,通过 objdump 来观察汇编指令,在我的 2.4 内核机器上,结果如下:
# gcc -O2 -c example1.c
# objdump -d example1.o
可以看到,编译器使用的是 jne (不相等跳转)指令,并且 else block 中的代码紧跟在后面。
8: 75 07 jne 11 <testfun+0x11>
a: b8 06 00 00 00 mov $0x6,%eax
第二个例子: example2.c
在这个例子中,我们认为 x 不为 0 的可能性更大
编译以后,通过 objdump 来观察汇编指令,在我的 2.4 内核机器上,结果如下:
# gcc -O2 -c example2.c
# objdump -d example2.o
这次编译器使用的是 je (相等跳转)指令,并且 if block 中的代码紧跟在后面。
8: 74 07 je 11 <testfun+0x11>
a: b8 19 00 00 00 mov $0x19,%eax
在GTK+2.0源码中有很多这样的宏:G_LIKELY和G_UNLIKELY。比如下面这段代码:
if (G_LIKELY (acat == 1)) /* allocate through magazine layer */
{
ThreadMemory *tmem = thread_memory_from_self();
guint ix = SLAB_INDEX (allocator, chunk_size);
if (G_UNLIKELY (thread_memory_magazine1_is_empty (tmem, ix)))
{
thread_memory_swap_magazines (tmem, ix);
if (G_UNLIKELY (thread_memory_magazine1_is_empty (tmem, ix)))
thread_memory_magazine1_reload (tmem, ix);
}
mem = thread_memory_magazine1_alloc (tmem, ix);
}
在源码中,宏G_LIKELY和G_UNLIKELY 是这么定义的:
#define G_LIKELY(expr) (__builtin_expect (_G_BOOLEAN_EXPR(expr), 1))
#define G_UNLIKELY(expr) (__builtin_expect (_G_BOOLEAN_EXPR(expr), 0))
宏_G_BOOLEAN_EXPR的作用是把expr转换为0和1,即真假两种。要理解宏G_LIKELY和G_UNLIKELY ,很明显必须理解__builtin_expect。__builtin_expect是GCC(version>=2.9)引进的宏,其作用就是帮助编译器判断条件跳转的预期值,避免跳转造成时间乱费。拿上面的代码来说:
if (G_LIKELY (acat == 1)) //表示大多数情况下if里面是真,程序大多数直接执行if里面的程序
而
if (G_UNLIKELY (thread_memory_magazine1_is_empty (tmem, ix)))//表示大多数情况if里面为假,程序大多数直接执行else里面的程序
可能大家看到还是一头雾水,看下面一段就会明白其中的乐趣啦;
//test_builtin_expect.c
#define LIKELY(x) __builtin_expect(!!(x), 1)
#define UNLIKELY(x) __builtin_expect(!!(x), 0)
int test_likely(int x)
{
if(LIKELY(x))
{
x = 5;
}
else
{
x = 6;
}
return x;
}
int test_unlikely(int x)
{
if(UNLIKELY(x))
{
x = 5;
}
else
{
x = 6;
}
return x;
}
[lammy@localhost test_builtin_expect]$ gcc -fprofile-arcs -O2 -c test_builtin_expect.c
[lammy@localhost test_builtin_expect]$ objdump -d test_builtin_expect.o
test_builtin_expect.o: file format elf32-i386
Disassembly of section .text:
00000000 <test_likely>:
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 8b 45 08 mov 0x8(%ebp),%eax
6: 83 05 38 00 00 00 01 addl $0x1,0x38
d: 83 15 3c 00 00 00 00 adcl $0x0,0x3c
14: 85 c0 test %eax,%eax
16: 74 15 je 2d <test_likely+0x2d>//主要看这里
18: 83 05 40 00 00 00 01 addl $0x1,0x40
1f: b8 05 00 00 00 mov $0x5,%eax
24: 83 15 44 00 00 00 00 adcl $0x0,0x44
2b: 5d pop %ebp
2c: c3 ret
2d: 83 05 48 00 00 00 01 addl $0x1,0x48
34: b8 06 00 00 00 mov $0x6,%eax
39: 83 15 4c 00 00 00 00 adcl $0x0,0x4c
40: 5d pop %ebp
41: c3 ret
42: 8d b4 26 00 00 00 00 lea 0x0(%esi,%eiz,1),%esi
49: 8d bc 27 00 00 00 00 lea 0x0(%edi,%eiz,1),%edi
00000050 <test_unlikely>:
50: 55 push %ebp
51: 89 e5 mov %esp,%ebp
53: 8b 55 08 mov 0x8(%ebp),%edx
56: 83 05 20 00 00 00 01 addl $0x1,0x20
5d: 83 15 24 00 00 00 00 adcl $0x0,0x24
64: 85 d2 test %edx,%edx
66: 75 15 jne 7d <test_unlikely+0x2d>//主要看这里
68: 83 05 30 00 00 00 01 addl $0x1,0x30
6f: b8 06 00 00 00 mov $0x6,%eax
74: 83 15 34 00 00 00 00 adcl $0x0,0x34
7b: 5d pop %ebp
7c: c3 ret
7d: 83 05 28 00 00 00 01 addl $0x1,0x28
84: b8 05 00 00 00 mov $0x5,%eax
89: 83 15 2c 00 00 00 00 adcl $0x0,0x2c
90: 5d pop %ebp
91: c3 ret
92: 8d b4 26 00 00 00 00 lea 0x0(%esi,%eiz,1),%esi
99: 8d bc 27 00 00 00 00 lea 0x0(%edi,%eiz,1),%edi
000000a0 <_GLOBAL__I_65535_0_test_likely>:
a0: 55 push %ebp
a1: 89 e5 mov %esp,%ebp
a3: 83 ec 08 sub
volatile 影响编译器编译的结果,指出,volatile 变量是随时可能发生变化的,与volatile变量有关的运算,不要进行编译优化,以免出错,(VC++ 在产生release版可执行码时会进行编译优化,加volatile关键字的变量有关的运算,将不进行编译优化。)。
例如:
volatile int i=10;
int j = i;
...
int k = i;
volatile 告诉编译器i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的可执行码会重新从i的地址读取数据放在k中。
而优化做法是,由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在k中。而不是重新从i里面读。这样以来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问,不会出错。
/**********************
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
1) 并行设备的硬件寄存器(如:状态寄存器)
2) 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3) 多线程应用中被几个任务共享的变量
回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。搞嵌入式的家伙们经常同硬件、中断、RTOS等等打交道,所有这些都要求用到volatile变量。不懂得volatile的内容将会带来灾难。假设被面试者正确地回答了这是问题(嗯,怀疑是否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile完全的重要性。
1)一个参数既可以是const还可以是volatile吗?解释为什么。
2); 一个指针可以是volatile 吗?解释为什么。
3); 下面的函数有什么错误:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
下面是答案:
1)是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
2); 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
3) 这段代码有点变态。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:
int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:
long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}
位操作(Bit manipulation)
//*********************
嵌入式编程中经常用到 volatile这个关键字,在网上查了下他的用法可以归结为以下两点:
一:告诉compiler不能做任何优化
比如要往某一地址送两指令:
int *ip =...; //设备地址
*ip = 1; //第一个指令
*ip = 2; //第二个指令
以上程序compiler可能做优化而成:
int *ip = ...;
*ip = 2;
结果第一个指令丢失。如果用volatile, compiler就不允许做任何的优化,从而保证程序的原意:
volatile int *ip = ...;
*ip = 1;
*ip = 2;
即使你要compiler做优化,它也不会把两次付值语句间化为一。它只能做其它的优化。这对device driver程序员很有用。
二:表示用volatile定义的变量会在程序外被改变,每次都必须从内存中读取,而不能把他放在cache或寄存器中重复使用。
如 volatile char a;
a=0;
while(!a){
//do some things;
}
doother();
如果没有 volatile doother()不会被执行
__asm__ __volatile__内嵌汇编用法简述 在阅读C/C++原码时经常会遇到内联汇编的情况,下面简要介绍下__asm__ __volatile__内嵌汇编用法。因为我们华清远见教学平台是ARM体系结构的,所以下面的示例都是用ARM汇编。
带有C/C++表达式的内联汇编格式为:
__asm__ __volatile__("Instruction List" : Output : Input : Clobber/Modify);
其中每项的概念及功能用法描述如下:
1、 __asm__
__asm__是GCC 关键字asm 的宏定义:
#define __asm__ asm
__asm__或asm 用来声明一个内联汇编表达式,所以任何一个内联汇编表达式都是以它开头的,是必不可少的。
2、Instruction List
Instruction List 是汇编指令序列。它可以是空的,比如:__asm__ __volatile__(""); 或 __asm__ ("");都是完全合法的内联汇编表达式,只不过这两条语句没有什么意义。但并非所有Instruction List 为空的内联汇编表达式都是没有意义的,比如:__asm__ ("":::"memory");
就非常有意义,它向GCC 声明:“内存作了改动”,GCC 在编译的时候,会将此因素考虑进去。 当在"Instruction List"中有多条指令的时候,可以在一对引号中列出全部指令,也可以将一条 或几条指令放在一对引号中,所有指令放在多对引号中。如果是前者,可以将每一条指令放在一行,如果要将多条指令放在一行,则必须用分号(;)或换行符(/n)将它们分开. 综上述:(1)每条指令都必须被双引号括起来 (2)两条指令必须用换行或分号分开。
例如: 在ARM系统结构上关闭中断的操作
int disable_interrupts (void)
{
unsigned long old,temp;
__asm__ __volatile__("mrs %0, cpsr/n"
"orr %1, %0, #0x80/n"
"msr cpsr_c, %1"
: "=r" (old), "=r" (temp)
:
: "memory");
return (old & 0x80) == 0;
}
3. __volatile__
__volatile__是GCC 关键字volatile 的宏定义
#define __volatile__ volatile
__volatile__或volatile 是可选的。如果用了它,则是向GCC 声明不允许对该内联汇编优化,否则当 使用了优化选项(-O)进行编译时,GCC 将会根据自己的判断决定是否将这个内联汇编表达式中的指令优化掉。
4、 Output
Output 用来指定当前内联汇编语句的输出
例如:从arm协处理器p15中读出C1值
static unsigned long read_p15_c1 (void)
{
unsigned long value;
__asm__ __volatile__(
"mrc p15, 0, %0, c1, c0, 0 @ read control reg/n"
: "=r" (value) @编译器选择一个R*寄存器
:
: "memory");
#ifdef MMU_DEBUG
printf ("p15/c1 is = %08lx/n", value);
#endif
return value;
}
5、 Input
Input 域的内容用来指定当前内联汇编语句的输入Output和Input中,格式为形如“constraint”(variable)的列表(逗号分隔)
例如:向arm协处理器p15中写入C1值
static void write_p15_c1 (unsigned long value)
{
#ifdef MMU_DEBUG
printf ("write %08lx to p15/c1/n", value);
#endif
__asm__ __volatile__(
"mcr p15, 0, %0, c1, c0, 0 @ write it back/n"
:
: "r" (value) @编译器选择一个R*寄存器
: "memory");
read_p15_c1 ();
}
6.、Clobber/Modify
有时候,你想通知GCC当前内联汇编语句可能会对某些寄存器或内存进行修改,希望GCC在编译时能够将这一点考虑进去。那么你就可以在Clobber/Modify域声明这些寄存器或内存。这种情况一般发生在一个寄存器出现在"Instruction List",但却不是由Input/Output操作表达式所指定的,也不是在一些Input/Output操作表达式使用"r"约束时由GCC 为其选择的,同时此寄存器被"Instruction List"中的指令修改,而这个寄存器只是供当前内联汇编临时使用的情况。
例如:
__asm__ ("mov R0, #0x34" : : : "R0");
寄存器R0出现在"Instruction List中",并且被mov指令修改,但却未被任何Input/Output操作表达式指定,所以你需要在Clobber/Modify域指定"R0",以让GCC知道这一点。
因为你在Input/Output操作表达式所指定的寄存器,或当你为一些Input/Output操作表达式使用"r"约束,让GCC为你选择一个寄存器时,GCC对这些寄存器是非常清楚的——它知道这些寄存器是被修改的,你根本不需要在Clobber/Modify域再声明它们。但除此之外, GCC对剩下的寄存器中哪些会被当前的内联汇编修改一无所知。所以如果你真的在当前内联汇编指令中修改了它们,那么就最好在Clobber/Modify 中声明它们,让GCC针对这些寄存器做相应的处理。否则有可能会造成寄存器的不一致,从而造成程序执行错误。
如果一个内联汇编语句的Clobber/Modify域存在"memory",那么GCC会保证在此内联汇编之前,如果某个内存的内容被装入了寄存器,那么在这个内联汇编之后,如果需要使用这个内存处的内容,就会直接到这个内存处重新读取,而不是使用被存放在寄存器中的拷贝。因为这个 时候寄存器中的拷贝已经很可能和内存处的内容不一致了。
这只是使用"memory"时,GCC会保证做到的一点,但这并不是全部。因为使用"memory"是向GCC声明内存发生了变化,而内存发生变化带来的影响并不止这一点。
例如:
int main(int __argc, char* __argv[])
{
int* __p = (int*)__argc;
(*__p) = 9999;
__asm__("":::"memory");
if((*__p) == 9999)
return 5;
return (*__p);
}
本例中,如果没有那条内联汇编语句,那个if语句的判断条件就完全是一句废话。GCC在优化时会意识到这一点,而直接只生成return 5的汇编代码,而不会再生成if语句的相关代码,而不会生成return (*__p)的相关代码。但你加上了这条内联汇编语句,它除了声明内存变化之外,什么都没有做。但GCC此时就不能简单的认为它不需要判断都知道 (*__p)一定与9999相等,它只有老老实实生成这条if语句的汇编代码,一起相关的两个return语句相关代码。
另外在linux内核中内存屏障也是基于它实现的include/asm/system.h中
# define barrier() _asm__volatile_("": : :"memory")
主要是保证程序的执行遵循顺序一致性。呵呵,有的时候你写代码的顺序,不一定是最终执行的顺序,这个是处理器有关的。
gobject中,基本的gtype, 以及继承,派生关系;
gobject 类初始化,对象初始化,接口初始化,方法调用,信号发射等;需要仔细的观察;
所以我们需要函数完成2个功能:
1. 函数调用层级关系显示(类初始化,对象初始化,接口初始化...)
2. gtype/gobject父子关系,显示
参考gobject-query.c, 修改一下:
gtype.h:
void g_type_get_node_tree(void); void g_type_enter_func(const char *name); void g_type_leave_func(const char *name); #define G_ENTER_FUNC g_type_enter_func(__FUNCTION__) #define G_LEAVE_FUNC g_type_leave_func(__FUNCTION__)
gtype.c:
static GType static_gtype_root = G_TYPE_OBJECT; static void _g_type_show_nodes (GType type, GType sibling, const gchar *indent) { static gchar *indent_inc = NULL; static guint spacing = 1; static gboolean recursion = TRUE; #define O_SPACE " " #define O_ESPACE "" #define O_BRANCH "+" #define O_VLINE "|" #define O_LLEAF "`" #define O_KEY_FILL "_" GType *children; guint i; if (!type) return; if (!indent_inc) { indent_inc = g_new (gchar, strlen (O_SPACE) + 1); *indent_inc = 0; strcpy (indent_inc, O_SPACE); } children = g_type_children (type, NULL); if (type != static_gtype_root) for (i = 0; i < spacing; i++) g_printf("%s%s\n", indent, O_VLINE); g_printf("%s%s%s%s", indent, sibling ? O_BRANCH : (type != static_gtype_root ? O_LLEAF : O_SPACE), O_ESPACE, g_type_name (type)); for (i = strlen (g_type_name (type)); i <= strlen (indent_inc); i++) g_printf("%s", O_KEY_FILL); g_printf("\n"); if (children && recursion) { gchar *new_indent; GType *child; if (sibling) new_indent = g_strconcat (indent, O_VLINE, indent_inc, NULL); else new_indent = g_strconcat (indent, O_SPACE, indent_inc, NULL); for (child = children; *child; child++) _g_type_show_nodes (child[0], child[1], new_indent); g_free (new_indent); } g_free (children); } void g_type_get_node_tree(void) { gint i; static_gtype_root = ~0; for (i = 0; i <= G_TYPE_FUNDAMENTAL_MAX; i += G_TYPE_MAKE_FUNDAMENTAL (1)) { const gchar *name = g_type_name (i); if (name) _g_type_show_nodes (i, 0, ""); } } static guint g_static_depth = 0; void g_type_enter_func(const char *name) { guint i; const char *s; if (g_static_depth == 1) g_printf(" \n"); for (i = 0; i < g_static_depth; i++) g_printf("+"); g_printf("Entering %s\n", name); g_static_depth++; } void g_type_leave_func(const char *name) { const char *s; guint i; if (g_static_depth != 0) g_static_depth--; if (g_static_depth < 0) g_static_depth = 0; for (i = 0; i < g_static_depth; i++) g_printf("-"); g_printf("Leaving %s\n", name); }
修改gobject/testgobject.c
可以看到的结果:
Entering main +Entering test_object_get_type ++Entering test_iface_get_type --Leaving test_iface_get_type -Leaving test_object_get_type +Entering iface_base_init -Leaving iface_base_init +Entering iface_base_init -Leaving iface_base_init +Entering test_object_class_init ++Entering test_iface_get_type --Leaving test_iface_get_type -Leaving test_object_class_init +Entering test_object_test_iface_init ++Entering test_iface_get_type --Leaving test_iface_get_type -Leaving test_object_test_iface_init +Entering derived_object_get_type ++Entering test_object_get_type --Leaving test_object_get_type ++Entering test_iface_get_type --Leaving test_iface_get_type -Leaving derived_object_get_type +Entering iface_base_init -Leaving iface_base_init +Entering derived_object_class_init -Leaving derived_object_class_init +Entering derived_object_test_iface_init ++Entering test_iface_get_type --Leaving test_iface_get_type -Leaving derived_object_test_iface_init +Entering test_object_init ++Entering test_object_get_type --Leaving test_object_get_type -Leaving test_object_init +Entering derived_object_init ++Entering derived_object_get_type --Leaving derived_object_get_type ++Entering test_object_get_type --Leaving test_object_get_type ++Entering test_object_get_type --Leaving test_object_get_type -Leaving derived_object_init +Entering test_object_check_private_init ++Entering test_object_get_type --Leaving test_object_get_type private data during initialization: 54321 == 54321 -Leaving test_object_check_private_init | `void | `GInterface | +GTypePlugin | `TestIface | `gchar | `guchar | `gboolean | `gint | `guint | `glong | `gulong | `gint64 | `guint64 | `GEnum | `GFlags | `gfloat | `gdouble | `gchararray | `gpointer | `GType | `GBoxed | `GValueArray | `GParam | +GParamChar | +GParamUChar | +GParamBoolean | +GParamInt | +GParamUInt | +GParamLong | +GParamULong | +GParamInt64 | +GParamUInt64 | +GParamUnichar | +GParamEnum | +GParamFlags | +GParamFloat | +GParamDouble | +GParamString | +GParamParam | +GParamBoxed | +GParamPointer | +GParamValueArray | +GParamObject | +GParamOverride | +GParamGType | `GParamVariant | `GObject | `TestObject | `DerivedObject | `GVariant | `FooShadow1 | `FooShadow2 +Entering test_object_get_type -Leaving test_object_get_type +Entering test_object_init ++Entering test_object_get_type --Leaving test_object_get_type -Leaving test_object_init MAIN: emit test-signal: TestObject-Message: ::test_signal default_handler called +Entering test_object_test_signal ++Entering test_iface_get_type --Leaving test_iface_get_type -Leaving test_object_test_signal +Entering test_signal_accumulator -Leaving test_signal_accumulator TestObject-Message: ::test_signal default_handler called +Entering test_object_test_signal ++Entering test_iface_get_type --Leaving test_iface_get_type -Leaving test_object_test_signal +Entering test_signal_accumulator -Leaving test_signal_accumulator TestObject-Message: ::test_signal default_handler called +Entering test_object_test_signal ++Entering test_iface_get_type --Leaving test_iface_get_type -Leaving test_object_test_signal TestObject-Message: signal return: "<default_handler><default_handler>" MAIN: call iface print-string on test and derived object: +Entering iface_print_string ++Entering test_iface_get_type --Leaving test_iface_get_type ++Entering test_iface_get_type --Leaving test_iface_get_type ++Entering print_foo Iface-FOO: "iface-string-from-test-type" from 0x9370a00 --Leaving print_foo -Leaving iface_print_string +Entering iface_print_string ++Entering test_iface_get_type --Leaving test_iface_get_type ++Entering test_iface_get_type --Leaving test_iface_get_type ++Entering print_bar +++Entering test_iface_get_type ---Leaving test_iface_get_type Iface-BAR: "iface-string-from-derived-type" from 0x936aaf0 chaining: +++Entering test_iface_get_type ---Leaving test_iface_get_type +++Entering print_foo Iface-FOO: "iface-string-from-derived-type" from 0x936aaf0 ---Leaving print_foo --Leaving print_bar -Leaving iface_print_string +Entering test_object_get_type -Leaving test_object_get_type private data after initialization: 54321 == 54321 Leaving main
转载时请注明出处和作者联系方式
作者联系方式:会飞的鱼 <parker30_liu at hotmail dot com>
刚开始接触GLib库时,对GLib传统的习惯和特有的概念不熟悉,在编写基于GLib的程序时总是很不顺手,心中很是不爽。后来随着写的代码多了,渐渐熟悉了GLib的这些术语、概念、编码风格,情况好多了。但对GLib的类型系统还是不大清楚,只不过是照葫芦画瓢学的不错而已,对基于GType的使用在较深入时还是一知半解。最近有了些空余时间,于是去仔细看了GType相关的代码,总算弄得比较清楚了。记录在此,以便查阅,有不对之处,请高手指正。
Gtype提供了GLib的数据类型系统和数据类型定义,是GObject对象系统的基础。GLib定义了一些基本数据类型,并允许用户定义一些基本类型的派生类型,还可以定义动态类型,如GObject的对象系统的各种类型的对象都是GObject的派生类型。GType只支持单继承,但允许通过接口继承的方式实现变形的多继承,避免了类似C++的多继承的复杂性和多继承导致的各种问题,规范了程序员对多继承的使用。GType提供了一个基于C语言的具有完备的多层抽象结构的继承体系的良好实现,使得我们可以使用C语言来进行面向对象的程序设计和实现。
首先,从GType这个数据类型的定义开始,一步一步地进入这个复杂的GType类型系统,让我们来解开这个迷宫吧。下面的分析是基于GLib的2.12.6版本的源代码来进行的,可能和较早的版本有些出入,请大家注意。
在最新的GLib中,GType是一个ulong类型,即无符号长整型数。GType的意义是用来指向整个GLib类型系统的一个类型节点。对于基本类型,GType是该类型在基本类型中的序号乘以4(乘以4是用来和非基本类型兼容,内存地址低二位为0)。对于非基本类型,GType是该类型的类型节点的类型信息的内存地址。
基本类型最多有256个,现在GLib只使用了49个,包括GLib已经定义的基本类型、GLib保留的基本类型、BSE保留的基本类型,其余的基本类型给用户使用。下面是GLib中已经定义的基本类型。
#define G_TYPE_INVALID G_TYPE_MAKE_FUNDAMENTAL (0) #define G_TYPE_NONE G_TYPE_MAKE_FUNDAMENTAL (1) #define G_TYPE_INTERFACE G_TYPE_MAKE_FUNDAMENTAL (2) #define G_TYPE_CHAR G_TYPE_MAKE_FUNDAMENTAL (3) #define G_TYPE_UCHAR G_TYPE_MAKE_FUNDAMENTAL (4) #define G_TYPE_BOOLEAN G_TYPE_MAKE_FUNDAMENTAL (5) #define G_TYPE_INT G_TYPE_MAKE_FUNDAMENTAL (6) #define G_TYPE_UINT G_TYPE_MAKE_FUNDAMENTAL (7) #define G_TYPE_LONG G_TYPE_MAKE_FUNDAMENTAL (8) #define G_TYPE_ULONG G_TYPE_MAKE_FUNDAMENTAL (9) #define G_TYPE_INT64 G_TYPE_MAKE_FUNDAMENTAL (10) #define G_TYPE_UINT64 G_TYPE_MAKE_FUNDAMENTAL (11) #define G_TYPE_ENUM G_TYPE_MAKE_FUNDAMENTAL (12) #define G_TYPE_FLAGS G_TYPE_MAKE_FUNDAMENTAL (13) #define G_TYPE_FLOAT G_TYPE_MAKE_FUNDAMENTAL (14) #define G_TYPE_DOUBLE G_TYPE_MAKE_FUNDAMENTAL (15) #define G_TYPE_STRING G_TYPE_MAKE_FUNDAMENTAL (16) #define G_TYPE_POINTER G_TYPE_MAKE_FUNDAMENTAL (17) #define G_TYPE_BOXED G_TYPE_MAKE_FUNDAMENTAL (18) #define G_TYPE_PARAM G_TYPE_MAKE_FUNDAMENTAL (19) #define G_TYPE_OBJECT G_TYPE_MAKE_FUNDAMENTAL (20)
|
宏G_TYPE_MAKE_FUNDAMENTAL是将类型序号乘以4以得到基本类型的值。除了基本数据使用的类型如G_TYPE_CHAR等外,还有如下几个基本类型。
G_TYPE_INVALID
无效的GType类型,此种类型不是GType类型。
G_TYPE_NONE
空的GType类型,和C语言的void相当。
G_TYPE_BOXED
多个GType类型的数据组合在一起的得到的GType类型,可以是单一GType类型数据组成的数组,也可以是不同GType类型数据组成的异质数组。如字符串数组(G_TYPE_STRV)、GValue数组(G_TYPE_VALUE_ARRAY)、日期数据类型(G_TYPE_DATE)。此类型只能单层继承。
G_TYPE_PARAM
参数数据类型,用于进行函数、信号等的参数传递的GType类型。
G_TYPE_OBJECT
GObject对象的GType类型,是GLib对象系统的根对象类型,是一个可以实例化的类类型,可以多层继承,但只能单根继承。G_TYPE_OBJECT类型的派生类型的实例化对象在不混淆的情况下也可以称为GObject对象。
G_TYPE_INTERFACE
接口类型,定义了对数据或对象进行操作的接口方法,凡实现了某一个接口的所有方法的GObject对象可以称其继承了该接口。
Gtype的基本类型是可以继承的,其继承关系由类型节点确定,类型节点保存了GType类型的类型信息。所有基本类型的类型节点的指针保存在一个基本类型节点数组中,基本类型的值除以4就是该基本类型节点在基本类型节点数组中的序号。
类型节点的数据结构如下所示。
struct _TypeNode { GTypePlugin *plugin; guint n_children : 12; guint n_supers : 8; guint _prot_n_ifaces_prerequisites : 9; guint is_classed : 1; guint is_instantiatable : 1; guint mutatable_check_cache : 1; /* combines some common path checks */ GType *children; TypeData * volatile data; GQuark qname; GData *global_gdata; union { IFaceEntry *iface_entries; /* for !iface types */ GType *prerequisistes; } _prot; GType supers[1]; /* flexible array */ }; |
类型节点数据结构中的supers和children确定了各个GType类型的继承关系。n_supers是所继承的所有祖先类型的个数,数组supers保存了该类型继承的所有祖先类型,n_children是所有子类型的个数,数组children保存了该类型的所有子类型。所有GType类型组成了下图所示的关系。
图中箭头由派生类指向基类,虚线框的节点类型是和我们的分析无关的继承关系。该图描述了无继承,单层继承,多层继承这几种继承关系。
基本类型的类型节点除了普通类型的信息外,还有基本类型的类型信息。基本类型的信息决定了该基本类型及其派生类型的如下几个特性。
G_TYPE_FLAG_CLASSED
该基本类型是一个类类型。
G_TYPE_FLAG_INSTANTIABLE
该基本类型是可以实例化的类型,即是否可以创建该类型的对象。
G_TYPE_DERIVABLE
该基本类型是可以单层继承的类型,只允许一层的平坦的继承。
G_TYPE_DEEP_DERIVABLE
该基本类型是可以多层继承的类型,可以有子类型、孙子类型,子子孙孙无穷尽也。
普通类型的信息有如下几个类型特性。
G_TYPE_FLAG_ABSTRACT
该类型是一个抽象类型。
G_TYPE_FLAG_VALUE_ABSTRACT
该类型是一个值抽象类型,是GValue用的,表明该类型的实例是一个GValue值
每个类型节点的祖先类型数组supers是按如下方式排列,祖先类型数组的大小是1(self) + n_supers + 1(0),n_supers最大值是255,即最大继承层次是255层。
自身类型 |
父类型 |
祖父类型 |
... |
基本类型 |
0 |
每个类型节点的子类型按创建的次序增加到子类型数组children中,n_children最大值是4095,即最多有4095个子类型。
类型节点数据结构的qname保存了该GType类型的名字,这是一个夸克(夸克是GLib的特有术语,相当于字符串的ID)。plugin是用于动态类型的类型数据入口。
is_classed指明该类型是否是一个类,is_instantiatable指明该类型是否可以实例化。这两个成员的值从基本类型继承而来。
global_gdata是GType的一个特色,用于保存GType类型相关的附属信息,其本身是一个由QData组成的动态数组GData。每个QData是个夸克指针对,夸克保存该信息的名字,指针指向该信息的内容。可以通过函数g_type_set_qdata()设置该类型相关的附属信息,通过函数g_type_get_qdata()获取该类型相关的附属信息。这是一个十分方便和好用的功能。
_prot是一个联合,当该类型是一个接口时,是一个由GType类型组成的动态数组,实现这个接口的GType必须要实现该GType类型数组中的所有GType类型,该数组中一般都是接口类型,最多只有一个可实例化的类型。若不是接口,是一般的类,则是一个由接口入口IFaceEntry组成的动态数组,这个数组保存了这个类继承的所有接口。_prot_n_ifaces_prerequisites是动态数组元素的个数。
data是该GType类型的类型数据,保存了和该类型相关的接口、类或实例对象的数据。类型数据的数据结构如下所示。
union _TypeData { CommonData common; IFaceData iface; ClassData class; InstanceData instance; }; |
这是一个联合,当类型是一个接口时,为接口数据IFaceData。当类型是一个类时,为类数据ClassData。当类型是一个对象实例时,为对象数据InstanceData。当类型是其他一般的类型时,为类型公共数据CommonData。接口数据IFaceData、类数据ClassData、对象数据InstanceData都包含有公共数据CommonData。
CommonData的数据结构如下所示。
struct _CommonData { guint ref_count; GTypeValueTable *value_table; }; |
ref_count是引用计数,是大部分面向对象系统用来保证对象能够在复杂的环境下正确的创建、引用、销毁的一种技术。
value_table是该gtype类型的值处理函数表,如值的初始化函数、值的释放函数、值拷贝函数等,这是同类型的gtype实例之间实现值拷贝的基础。下面是GTypeValueTable的数据结构。
struct _GTypeValueTable { void (*value_init) (GValue *value); void (*value_free) (GValue *value); void (*value_copy) (const GValue *src_value, GValue *dest_value); /* varargs functionality (optional) */ gpointer (*value_peek_pointer) (const GValue *value); gchar *collect_format; gchar* (*collect_value) (GValue *value, guint n_collect_values, GTypeCValue *collect_values, guint collect_flags); gchar *lcopy_format; gchar* (*lcopy_value) (const GValue *value, guint n_collect_values, GTypeCValue *collect_values, guint collect_flags); }; |