Socket与系统调用深度分析

为了系统的安全以及操作的便利性,当用户进程需要内核的服务时,并不是直接请求,而是间接的通过系统调用来获取。

运行模式和地址空间

(1)运行模式

  内核模式和用户模式。

(2)地址空间

  进程拥有的地址空间分为用户空间和内核空间(位置固定)。在用户态下只能访问用户空间;而在核心态下两者皆可访问。因此当用户进程需要访问内核的资源时,要从用户态切换到核心态。

系统调用、应用编程接口(API)和C库

(1)系统调用

  系统调用是通过软中断向内核发出一个明确的请求,每个系统调用对应一个封装例程。

(2)API

  API是一组函数定义,这些函数说明了如何获得一个给定的服务,一些API应用了封装例程,此外API还包括各种编程接口。

(3)C库

  C库中实现了Linux的主要API,包括标准C库函数和系统调用。

操作系统命令和内核函数

(1)操作系统命令

  操作系统命令比API更高一层,每个操作系统命令都是一个可执行程序(如ls、hostname等),它的实现调用了系统调用,可以通过strace命令查看其具体调用了的哪些系统调用。

(2)内核函数

  内核函数形式上普通函数一样,但它是在内核实现的,需要遵循内核编程的要求。在进入内核后,不同的系统调用会找到各自对应的内核函数,这些内核函数被称为系统调用的“服务例程”。

系统调用可归结为以下几步:

  1)程序调用libc库的封装函数
  2)调用软中断int 0x80进入内核
  3)在内核中首先执行entry_INT80_32函数,接着根据系统调用号在系统调用表中查找到对应的系统调用服务例程
  4)执行该服务例程
  5)执行完毕后,从系统调用返回
接下来,以TCP/IP协议栈为例,利用socket编程应用,对调用过程加以分析和说明。

Linux初始化过程中加载TCP/IP协议栈

(1)start_kernel函数

  start_kernel是Linux内核启动的起点,以下是该函数的部分代码。

asmlinkage __visible void __init start_kernel(void)
{
         ...
	/* Do the rest non-__init'ed, we're now alive */
	rest_init();#调用了rest_init函数
}
(2)rest_init函数
static noinline void __init_refok rest_init(void)
{
         ...
	kernel_thread(kernel_init, NULL, CLONE_FS);#调用了kernel_init函数
         ...
}
(3)kernel_init函数
static int __ref kernel_init(void *unused)
{
        int ret;
	kernel_init_freeable();#调用了kernel_init_freeable函数,该函数所做的初始化工作中与网络初始化有关的主要在do_basic_setup函数中。
         ...
}
(4)kernel_init_freeable函数
 static noinline void __init kernel_init_freeable(void)
 {
         ...
 	do_basic_setup(); #调用了do_basic_setup函数
         ...
}
(4)do_basic_setup函数
 static void __init do_basic_setup(void)
 {
 	cpuset_init_smp();
 	usermodehelper_init();
 	shmem_init();
 	driver_init();
 	init_irq_proc();
 	do_ctors();
 	usermodehelper_enable();
 	do_initcalls();#调用了do_initcalls函数,该函数对一些子系统进行了初始化,这其中就包括了对TCP/IP网络协议栈的初始化。
 	random_int_secret_init();
}
(5)do_initcalls函数
 static void __init do_initcalls(void)
 {
 	int level; 
 	for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
 		do_initcall_level(level);
 }

do_initcalls函数是table驱动的,维护一个initcalls的table,对每一个注册进来的项目进行初始化。

(6)inet_init函数

  inet_init函数是TCP/IP协议栈初始化的入口函数,通过fs_initcall(inet_init)将函数注册进initcalls的table.

 static int __init inet_init(void)
 {
       ...
 } 
 fs_initcall(inet_init);

通过gdb调试设置断点的方式来验证这一调用过程。

Linux内核中的socket接口层代码

(1)对socket接口函数编号
 #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)		*/
(2)112号系统调用socketcall
/*
  *	System call vectors.
  *
  *	Argument checking cleaned up. Saved 20% in size.
  *  This function doesn't need to set the kernel lock because
  *  it is set by the callees.
  */
 
 SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)
 {
...
 	switch (call) {
	case SYS_SOCKET:
		err = sys_socket(a0, a1, a[2]);
		break;
	case SYS_BIND:
		err = sys_bind(a0, (struct sockaddr __user *)a1, a[2]);
		break;
	case SYS_CONNECT:
		err = sys_connect(a0, (struct sockaddr __user *)a1, a[2]);
		break;
	case SYS_LISTEN:
		err = sys_listen(a0, a1);
		break;
	case SYS_ACCEPT:
		err = sys_accept4(a0, (struct sockaddr __user *)a1,
				  (int __user *)a[2], 0);
		break;
	case SYS_GETSOCKNAME:
		err =
		    sys_getsockname(a0, (struct sockaddr __user *)a1,
				    (int __user *)a[2]);
		break;
	case SYS_GETPEERNAME:
		err =
		    sys_getpeername(a0, (struct sockaddr __user *)a1,
				    (int __user *)a[2]);
		break;
	case SYS_SOCKETPAIR:
		err = sys_socketpair(a0, a1, a[2], (int __user *)a[3]);
		break;
	case SYS_SEND:
		err = sys_send(a0, (void __user *)a1, a[2], a[3]);
		break;
	case SYS_SENDTO:
		err = sys_sendto(a0, (void __user *)a1, a[2], a[3],
				 (struct sockaddr __user *)a[4], a[5]);
		break;
	case SYS_RECV:
		err = sys_recv(a0, (void __user *)a1, a[2], a[3]);
        	break;
 	case SYS_RECVFROM:
 		err = sys_recvfrom(a0, (void __user *)a1, a[2], a[3],
 				   (struct sockaddr __user *)a[4],
 				   (int __user *)a[5]);
 		break;
 	case SYS_SHUTDOWN:
 		err = sys_shutdown(a0, a1);
 		break;
 	case SYS_SETSOCKOPT:
 		err = sys_setsockopt(a0, a1, a[2], (char __user *)a[3], a[4]);
 		break;
 	case SYS_GETSOCKOPT:
 		err =
 		    sys_getsockopt(a0, a1, a[2], (char __user *)a[3],
 				   (int __user *)a[4]);
 		break;
 	case SYS_SENDMSG:
 		err = sys_sendmsg(a0, (struct msghdr __user *)a1, a[2]);
 		break;
 	case SYS_SENDMMSG:
 		err = sys_sendmmsg(a0, (struct mmsghdr __user *)a1, a[2], a[3]);
 		break;
 	case SYS_RECVMSG:
 		err = sys_recvmsg(a0, (struct msghdr __user *)a1, a[2]);
 		break;
 	case SYS_RECVMMSG:
 		err = sys_recvmmsg(a0, (struct mmsghdr __user *)a1, a[2], a[3],
 				   (struct timespec __user *)a[4]);
 		break;
 	case SYS_ACCEPT4:
 		err = sys_accept4(a0, (struct sockaddr __user *)a1,
 				  (int __user *)a[2], a[3]);
 		break;
 	default:
 		err = -EINVAL;
 		break;
 	}
 	return err;
 }

再通过gdb调试,设置socket接口函数的断点进行验证。

参考链接:
https://blog.csdn.net/cs2539263027/article/details/78977054
https://github.com/mengning/net/blob/master/np2019.md

posted on 2019-12-19 20:43  MinHui  阅读(235)  评论(0)    收藏  举报

导航