API:(Application Programming Interface,应用程序编程接口)
指的是我们用户程序编程调用的如read(),write(),malloc(),free()之类的调用的是glibc库提供的库函数。API直接提供给用户编程使用,运行在用户态。 我们经常说到的POSIX(Portable Operating System Interface of Unix)是针对API的标准,即针对API的函数名,返回值,参数类型等。POSIX兼容也就指定这些接口函数兼容,但是并不管API具体如何实现。 系统调用
通过软中断或系统调用指令向内核发出一个明确的请求,内核将调用内核相关函数来实现(如sys_read(),sys_write(),sys_fork())。用户程序不能直接调用这些Sys_read,sys_write等函数。这些函数运行在内核态。 两者关系:
通常API函数库(如glibc)中的函数会调用封装例程,封装例程负责发起系统调用(通过发软中断或系统调用指令),这些都运行在用户态。内核开始接收系统调用后,cpu从用户态切换到内核态(cpu处于什么状态,程序就叫处于什么状态,所以很多地方也说程序从用户态切换到内核态,实际是cpu运行级别的切换,通常cpu 运行在3级表示用户态,cpu 运行在0级表示内核态),内核调用相关的内核函数来处理再逐步返回给封装例程,cpu进行一次内核态到用户态的切换,API函数从封装例程拿到结果,再处理完后返回给用户。
但是API函数不一定需要进行系统调用,如某些数学函数,没有必要进行系统调用,直接glibc里面就给处理了,整个过程运行在用户态。 所以作为我们编写linux用户程序的时候,是不能直接调用内核里面的函数的,内核里面的函数位于进程虚拟地址空间里面的内核空间,用户空间函数及函数库都处于进程虚拟地址空间里面的用户空间,用户空间调用内核空间的函数只有一个通道,这个通道就是系统调用指令,所以通常要调用glibc等库的接口函数,glibc也是用户空间的,但glibc自己实现了调用特殊的宏汇编系统调用指令进行cpu运行状态的切换,把进程从用户空间切换到内核空间。 用户态xyz()函数,内核最终一般会调用形如sys_xyz()的服务例程来处理(不过也有一些例外,这里暂时不考虑)
函数xyz()是直接提供给用户编程使用的。图中“SYSCALL”,“SY***IT”表示真正的汇编指令(汇编指令具体调用的是哪个暂时不关心,我们只需在此关注发起和退出了一个系统调用)。
发起系统调用:xyz()函数执行的过程中会执行SYSCALL汇编指令,此指令将会把cpu从用户态切换到内核态。SYACALL汇编指令中会包含将要调用的内核函数的系统调用号和参数,内核在上图系统调用处理程序中去查一个sys_call_talbe数组来找到这个系统调用号对应的服务例程(如sys_xyz())函数的地址,然后调用这个地址的函数执行。(这里glibc里面的系统调用号和内核里面的系统调用号必须完全相等,当然,这是约定好的) 系统调用返回:服务例程(如sys_xyz())函数返回值一般返回正数和0表示系统调用成功结束,而负数表示一个出错条件。紧接着SY***IT退出系统调用,此指令将cpu从内核态切换到用户态,glibc针对系统调用返回值如果出错则需要设置好errno(通常在c库头文件/usr/include/errno.h中),然后返回一个值做为glibc封装例程的返回值(如xyz()的返回值)。这里errno是libc自己用来定义的出错码,不一定是最后gblic封装例程的返回值 这里涉及到几个概念需要好好讲讲: 1.系统调用号
2.参数传递
3.SYSCALL,SY***IT进入退出系统调用
用source insight在linux内核中查找sys_open函数
刚开始我搜索“sys_open”,苦逼的找了几遍没找到具体实现的地方,都是调用这个函数的地方。。后经伯松提醒,可能被宏给替换了。。后想起代码中出现过的SYSCALL_DEFINE宏,就进行了给name加上”sys_”前缀,所以找到SYSCALL_DEFINE中含有open的这句
点击(此处)折叠或打开
深刻怀疑SYSCALL_DEFINE,其定义如下(在include/linux/syscalls.h中)
#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__) //注意,这里name已经变成了”_name”,加上了下划线了,所以”open”变成了“_open”了 点击(此处)折叠或打开
进一步
#define SYSCALL_DEFINEx(x, sname, ...) __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)//前面name已经加上“_open”了,记住 进一步 #define __SYSCALL_DEFINEx(x, name, ...) asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__))//这里,变成了“sys_open”了 所以,这里变成了 asmlinkage long sys_open(__SC_DECL3(__VA_ARGS__)) 而再进一步 #define __SC_DECL1(t1, a1) t1 a1 #define __SC_DECL2(t2, a2, ...) t2 a2, __SC_DECL1(__VA_ARGS__) #define __SC_DECL3(t3, a3, ...) t3 a3, __SC_DECL2(__VA_ARGS__),这里变成了asmlinkage long sys_open(const char __user* filename,__SC_DECL2(__VA_ARGS__)) 进一步变成了 asmlinkage long sys_open(const char __user* fliename,int flags,__SC_DECL1(t1,a1)) 进一步变成了 asmlinkage long sys_open(const char __user* filename,int flags,umode_t mode)了! 所以,最终变成了 点击(此处)折叠或打开
补充说明:宏定义中出现的#,##,...和__VA_ARGS_的特殊说明
# 这个是一个字符串替换,如#define S(x) “a”#x S(1),则变成了字符串“a1”了 ##,这个是个简单的替换
如#define T(x) a##x T(1)则变成了 a1 了,比如你前面定义了int a1=2; 就可以printf”%d”,T(1)),即等价于printf(“%d”,a1);如果你用来上面的S(1)替换T(1)那就变成了printf(“%d”,“a1”)肯定就不对了,或者你#define S(x) a#x,用S(1)替换T(1)那就变成了printf(“%d”,a”1”)了,肯定也不对了,所以,用“##”有的时候也是必须 的 …
宏定义的参数,如#define X(...) printf(“%s %s”,__VA_ARGS__); X(“a”,”b”)就变成了printf(“%s %s”,a,b);了 __VA_ARGS__就表示吧...参数给完整替换掉,”__VA_ARGS__”这个字符串缺一个字符都不可以。。 另外“#,##,...和__VA_ARGS_”也可参看http://www.cnblogs.com/zhujudah/archive/2012/03/22/2411240.html 的一些讲解。 测试例程:
点击(此处)折叠或打开
输出
2 a1 1 1 可以看到宏中带括号和不带括号是一样的效果 参考
1.《深入理解linux内核(第三版)》, 2. kernel-3.6.7源码 3.http://www.cnblogs.com/zhujudah/archive/2012/03/22/2411240.html |
|
来自: astrotycoon > 《debug》