socket与linux内核的关系图,Socket与系统调用深度分析

在linux中,将程序的运行空间分为内核空间与用户空间(内核态和用户态),在逻辑上它们之间是相互隔离的,因此用户程序不能访问内核数据,也无法使用内核函数。当用户进程必须访问内核或使用某个内核函数时,就得使用系统调用(System Call)。在Linux中,系统调用是用户空间访问内核空间的唯一途径.

《一》 Socket API编程接口:

(1)API:应用编程接口,即应用程序与系统之间的接口,本质是一些预先定义的函数集合,

功能:应用程序或开发人员可利用API访问系统中的资源和取得 OS 的服务(例如利用API访问一组例程),实现计算机软件之间的相互通信。

(2)Socket:对Socket理解为一种特殊的文件,是对“open—write/read—close”模式的一种实现,一些socket函数就是对其进行的操作(读/写IO、打开、关闭),包括socket()、bind()、listen()、connect()、accept()、read()、write()以及close()等函数.

《二》 系统调用机制:

系统调用:就是一种特殊的接口。通过这个接口,用户可以访问内核空间。系统调用规定了用户进程进入内核的具体位置。

系统调用是用户进程进入内核的接口层,它本身并非内核函数,但它是由内核函数实现,进入内核后,不同的系统调用会找到各自对应的内核函数,这些内核函数被称为系统调用的“服务例程”。比如系统调用getpid实际调用了服务例程为sys_getpid(),或者说系统调用getpid是服务例程sys_getpid()的“封装例程”。

具体步骤:用户进程-->系统调用-->内核-->返回用户空间。

系统调用就是为了解决上述问题而引入的,是提供给用户的“特殊接口”。

系统调用规定用户进程进入内核空间的具体位置。

(1).程序运行空间从用户空间进入内核空间。

(2)处理完后再返回用户空间。

《三》 API与系统调用的区别和联系

(1)区别:API是函数的定义,规定了这个函数的功能,跟内核无直接关系。而系统调用是通过中断向内核发请求,实现内核提供的某些服务。

(2)联系:程序员调用的是API(API函数),然后通过与系统调用共同完成函数的功能。   因此,API是一个提供给应用程序的接口,一组函数,是与程序员进行直接交互的。系统调用则不与程序员进行交互的,它是根据API函数,通过一个软中断机制向内核提交请求,以获取内核服务的接口。

《四》  Socket系统调用过程分析

1、   函数原型:int socket(int domain, int type, int protocol);

2、   内核实现源码分析

SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)

{intretval;struct socket *sock;intflags;

...

retval= sock_create(family, type, protocol, &sock);if (retval < 0)goto out;

retval= sock_map_fd(sock, flags & (O_CLOEXEC |O_NONBLOCK));if (retval < 0)gotoout_release;out:/*It may be already another descriptor 8) Not kernel problem.*/

returnretval;

out_release:

sock_release(sock);returnretval;

}

下面主要分析sock_create和sock_map_fd这两个函数,看它们是怎么在内核一步步创建和管理我们使用的socket。

(1)sock_create函数

sock_create() 实际调用的是 __sock_create()。

int __sock_create(struct net *net, int family, int type, intprotocol,struct socket **res, intkern)

{interr;struct socket *sock;const struct net_proto_family *pf;

...

err= security_socket_create(family, type, protocol, kern);//SElinux相关,暂不关注

/** Allocate the socket and allow the family to set things up. if

* the protocol is 0, the family is instructed to select an appropriate

* default.*/sock= sock_alloc();//创建struct socket结构体

sock->type = type;//设置套接字的类型

...

pf= rcu_dereference(net_families[family]);//获取对应协议族的协议实例对象

...

err= pf->create(net, sock, protocol, kern);if (err < 0)gotoout_module_put;

...

err= security_socket_post_create(sock, family, type, protocol, kern);//SElinux相关,暂不关注

*res =sock;

...

}

其中sock_alloc()和pf->create()这两个函数比较重要,Sock_alloc()函数分配一个struct socket_alloc结构体,将sockfs相关属性填充在socket_alloc结构体的vfs_inode变量中,以限定后续对这个sock文件允许的操作。同时sock_alloc()最终返回socket_alloc结构体的socket变量,用于后续操作。

pf->create调用的就是inet_create()函数

static int inet_create(struct net *net, struct socket *sock, intprotocol,intkern)

{struct sock *sk;struct inet_protosw *answer;struct inet_sock *inet;struct proto *answer_prot;

unsignedcharanswer_flags;charanswer_no_check;int try_loading_module = 0;interr;

...

sock->state =SS_UNCONNECTED;/*Look for the requested type/protocol pair.*/lookup_protocol:

err= -ESOCKTNOSUPPORT;

rcu_read_lock();//根据socket传入的protocal在inetsw[]数组中查找对应的元素

list_for_each_entry_rcu(answer, &inetsw[sock->type], list) {

err= 0;/*如果我们在socket的protocal传入的是6,即TCP协议,那么走这个分支*/

if (protocol == answer->protocol) {if (protocol !=IPPROTO_IP)break;

}else{/*如果socket的protocal传入的是0,那么走这个分支*/

if (IPPROTO_IP ==protocol) {

protocol= answer->protocol;//重新给protocal赋值,因此socket中protocal传入的是0或者6,都是可以的

break;

}if (IPPROTO_IP == answer->protocol)break;

}

err= -EPROTONOSUPPORT;

}

...

sock->ops = answer->ops;//将查找到的对应协议族的协议函数操作集赋值给我们之前创建的socket

answer_prot = answer->prot;

...//创建sock结构体,注意这里创建的结构体类型是sock,之前我们通过sock_alloc创建的结构体类型是socket

sk =sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot);if (sk ==NULL)goto out;

...

sock_init_data(sock, sk);//sock初始化//另一部分初始化,里面有对各个socket连接定时器的初始化

if (sk->sk_prot->init) {

err= sk->sk_prot->init(sk);if(err)

sk_common_release(sk);

}

...

}

}(2)sock_map_fd()

这个函数主要有两个部分,一个是创建file文件结构,fd文件描述符,另一部分是将file文件结构和fd文件描述符关联,同时将上一步返回的socket也一起绑定,形成一个完整的逻辑

static int sock_map_fd(struct socket *sock, intflags)

{struct file *newfile;//获取一个未使用的文件描述符,文件描述符的管理这里就暂不分析了

int fd =get_unused_fd_flags(flags);if (unlikely(fd < 0))returnfd;//分配file结构体

newfile =sock_alloc_file(sock, flags, NULL);if (likely(!IS_ERR(newfile))) {

fd_install(fd, newfile);returnfd;

}

...

}

至此,socket系统调用就结束了,将fd返回用户使用。

综上,socket系统调用的操作:首先在内核生成一个socket_alloc 和tcp_sock类型的对象,其中sock_alloc对象中的socket和tcp_sock对象的sock绑定,sock_alloc对象中的inode和file类型对象绑定。然后将分配的文件描述符fd和file对象关联,最后将这个文件描述符fd返回给用户使用。

经过这一连串操作,用户只要使用fd,内核就能根据这个fd进行网络连接管理的各种操作。

《五》跟踪分析Socket相关系统调用的内核处理函数

本次跟踪分析基于上次构建的MenuOS系统, 通过gdb设置断点来跟踪分析socket内核处理函数;

在linux-5.0.1目录下打开新的终端,执行命令,如下:

gdb

file vmlinux

target remote:1234b __sys_socket

b __sys_bind

b __sys_listen

b __sys_shutdown

通过以上指令,系统会进入gdb模式,通过file vmlinux加载vmlinux,用target remote:1234和menuos连接, 后面指令为设置的端点,执行到对应函数时,程序暂停,按c执行下去。结果显示如下,可见在replyhi 执行过程中,调用了socket()、bind()、listen()等API函数,实验完成。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值