1.Linux系统调用
1.1为什么用户程序不能直接访问系统内核提供的服务?
- 这是由于在Linux中,为了更好地保护内核空间,将程序的运行空间分为内核空间和用户空间(也就是常称的内核态和用户态),它们分别运行在不同的级别上,在逻辑上是相互隔离的。
- 用户进程在通常情况下不允许访问内核数据,也无法直接调用内核函数,它们只能在用户空间操作用户数据,调用用户空间的函数。
- 当用户空间的进程需要获得一定的系统服务时,应用程序调用系统调用,这时操作系统就根据系统调用号(每个系统调用被赋予一个系统调用号)使用户进程进入内核空间的具体位置调用相应的内核代码。
- 进行系统调用时,程序运行空间需要从用户空间进入内核空间,处理完后再返回到用户空间。
1.2 socket是如何进行系统调用的
系统调用的实质是调用内核函数,于内核态中运行,Linux中的用户通过执行一条访管指令“int $0x80”来调用系统调用,该指令会产生一个访管中断,从而让系统暂停当前的进程执行,而转去执行系统调用处理程序。通过用户态传入的系统调用号从系统调用表中找到相应的服务例程的入口并执行,完成后返回。
系统调用号与系统调用表:Linux内核中设置了一张系统调用表,用于关联系统调用号及其相对应的服务例程入口地址,定义在./arch/x86/entry/syscalls/syscall_64.tbl文件中,每个系统调用占一个表项,一旦分配好就不可以有任何变更。
Linux的系统调用都为SYSCALL_DEFINEx,为什么要定义成SYSCALL_DEFINE,主要就是将系统调用的参数统一变为了使用long型来接收,再强转转为int,也就是系统调用本来传下来的参数类型。详细过程可参考 https://blog.csdn.net/hxmhyp/article/details/22699669
在net/socket.c中有一个函数SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)便是socket调用的入口。
所有的socket系统调用的总入口是sys_socketcall(),在include/linux/Syscalls.h中定义
@param call 标识接口编号, @param args 是接口参数指针
接口编号的定义在 include/uapi/linux/net.h中定义,可以看到内核提供了20个socket系统调用的接口。
#ifndef _UAPI_LINUX_NET_H
#define _UAPI_LINUX_NET_H
#include
#include
#define NPROTO AF_MAX
#define SYS_SOCKET 1 /* sys_socket(2) */
#define SYS_BIND 2 /* sys_bind(2) */
#define SYS_CONNECT 3 /* sys_connect(2) */
#define SYS_LISTEN 4 /* sys_listen(2) */
#define SYS_ACCEPT 5 /* sys_accept(2) */
#define SYS_GETSOCKNAME 6 /* sys_getsockname(2) */
#define SYS_GETPEERNAME 7 /* sys_getpeername(2) */
#define SYS_SOCKETPAIR 8 /* sys_socketpair(2) */
#define SYS_SEND 9 /* sys_send(2) */
#define SYS_RECV 10 /* sys_recv(2) */
#define SYS_SENDTO 11 /* sys_sendto(2) */
#define SYS_RECVFROM 12 /* sys_recvfrom(2) */
#define SYS_SHUTDOWN 13 /* sys_shutdown(2) */
#define SYS_SETSOCKOPT 14 /* sys_setsockopt(2) */
#define SYS_GETSOCKOPT 15 /* sys_getsockopt(2) */
#define SYS_SENDMSG 16 /* sys_sendmsg(2) */
#define SYS_RECVMSG 17 /* sys_recvmsg(2) */
#define SYS_ACCEPT4 18 /* sys_accept4(2) */
#define SYS_RECVMMSG 19 /* sys_recvmmsg(2) */
#define SYS_SENDMMSG 20 /* sys_sendmmsg(2) */
每个系统调用都对应一个内核服务例程来实现系统调用的功能,与SYSCALL_DEFINEx对应,其命名的格式都是以"sys_开头。其代码通常放在./kernel/sys.c中,服务例程的原型声明则是放在./include/linux/syscall.h中。如sys_socket,通常格式是asmlinkage long sys_socket(int flag......)。其中的amslinkage是一个必需的限定词,用于通知编译器从堆栈中提取函数的参数,而不是从寄存器中。
socket的内核服务例程
* net/socket.c */
asmlinkage long sys_socket(int, int, int);
asmlinkage long sys_socketpair(int, int, int, int __user *);
asmlinkage long sys_bind(int, struct sockaddr __user *, int);
asmlinkage long sys_listen(int, int);
asmlinkage long sys_accept(int, struct sockaddr __user *, int __user *);
asmlinkage long sys_connect(int, struct sockaddr __user *, int);
asmlinkage long sys_getsockname(int, struct sockaddr __user *, int __user *);
asmlinkage long sys_getpeername(int, struct sockaddr __user *, int __user *);
asmlinkage long sys_sendto(int, void __user *, size_t, unsigned,
struct sockaddr __user *, int);
asmlinkage long sys_recvfrom(int, void __user *, size_t, unsigned,
struct sockaddr __user *, int __user *);
asmlinkage long sys_setsockopt(int fd, int level, int optname,
char __user *optval, int optlen);
asmlinkage long sys_getsockopt(int fd, int level, int optname,
char __user *optval, int __user *optlen);
asmlinkage long sys_shutdown(int, int);
asmlinkage long sys_sendmsg(int fd, struct user_msghdr __user *msg, unsigned flags);
asmlinkage long sys_recvmsg(int fd, struct user_msghdr __user *msg, unsigned flags);
接口编号对应的参数个数在net/socket.c文件中的nargs数组中定义
通过以上分析,我们知道了,socket的调用过程是通过中断向量进入系统调用,并且由用户态转为内核态,通过系统调用表,进入socket系统调用的总入口sys_socketcall(),再根据其传递的参数,调用具体的sys_socket(),过程如下图。
2.GDB追踪socket的内核调用过程
emm,Ubuntu被我玩坏了,现在重新升级内核中。。。。。。