3-socket的实践到内核--追踪socket到内核 .

http://blog.csdn.net/embeddedfly/article/details/6411703

前边二节我们看了关于socket的一些主要的实践练习,这些练习是对于我们进入内核追踪内核的socket过程非常重要,这节我们从应用程序的实践开始进入内核看是如何实现的,首先我们把前边二节的几个应用程序界面函数重新放在这里供大家参照,我们强调一点,有可能朋友们对我所说的应用程序界面的说法有点模糊,其实就是说我们在实践练习中所使用的函数,这些函数是通过C库间接运行系统调用的,表面上看起来能过C库的c.lib提供的诸多库函数进行工作的,其实他们最终都统一归结到内核的sys_socketcall()代码上,下面是前面几节我们看到的应用程序界面
 

socket(AF_UNIX, SOCK_STREAM, 0);
connect(sockfd, (struct sockaddr *)&address, len);
bind(server_sockfd, (struct sockaddr *)&server_address, server_len);
listen(server_sockfd, 5);
accept(server_sockfd,(struct sockaddr *)&client_address, client_len);

前面练习中我们用到上面这些函数,这些函数全部要通过sys_socketcall()系统调用,关于系统调用以前说过我们将会在关于中断的练习中详细论述,这里只要知道已经到达了系统调用函数处,这个函数在net/socket.c的2007行,注意本文针对的内核版本是2.6.26,我们再把内核代码贴在这里

/*
 *    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.
 */


asmlinkage long sys_socketcall(int call, unsigned long __user *args)
{
    unsigned long a[6];
    unsigned long a0, a1;
    int err;

    if (call < 1 || call > SYS_RECVMSG)
        return -EINVAL;

    /* copy_from_user should be SMP safe. */
    if (copy_from_user(a, args, nargs[call]))
        return -EFAULT;

    err = audit_socketcall(nargs[call] / sizeof(unsigned long), a);
    if (err)
        return err;

    a0 = a[0];
    a1 = a[1];

    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_accept(a0, (struct sockaddr __user *)a1,
             (int __user *)a[2]);
        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_RECVMSG:
        err = sys_recvmsg(a0, (struct msghdr __user *)a1, a[2]);
        break;
    default:
        err = -EINVAL;
        break;
    }
    return err;
}

这个函数是内核socket的总系统调用入口,参数call是具体的操作码,参数args是一个数组指针。另外我们需要明确从用户空间复制的参数数量,这是根据nargs[]来决定的,以call为下标将会从该数组中找到参数的个数,依据个数来把args处的参数从用户空间即我们的应用程序复制过来

/* Argument list sizes for sys_socketcall */
#define AL(x) ((x) * sizeof(unsigned long))
static const unsigned char nargs[18]={
    AL(0),AL(3),AL(3),AL(3),AL(2),AL(3),
    AL(3),AL(3),AL(4),AL(4),AL(4),AL(6),
    AL(6),AL(2),AL(5),AL(5),AL(3),AL(3)
};
#undef AL

可以看到上面的nargs数组中可以看出是规定了参数的个数,根据AL的宏解释,我们还可以看到在include/linux/net.h中规定了call的详细数字

#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)        */

这里就可以确定上面数组中call具体数字了。有可能朋友们会对注释中的(2)有疑惑,其的含义喻为这里是系统调用号。好了,我们将围绕sys_socketcall()系统调用函数展开具体的追踪过程,这里我们先看应用程序界面

socket(AF_UNIX, SOCK_STREAM, 0);

尽管在我们第二个练习中有还有另一个界面函数

socket(AF_INET, SOCK_STREAM, 0);

可以对比二者,也就是我们二个练习的socket类型不同之处一个是AF_UNIX另一个则是AF_INET,我们在前面二节中的题目中也加以区分了一个是unix本机内部的socket另一个则是以IP的socket,这二种都会统一进入系统调用sys_socketcall(),我们看一下上面的系统调用部分

    case SYS_SOCKET:
        err = sys_socket(a0, a1, a[2]);

有朋友可能会被我贴的代码所吓倒,会问“这里只需要一个函数sys_socket()”那贴出全代码做什么,我们上面贴出sys_socketcall()的整个函数代码是为了让大家先对其有个整体的了解,今后我们只提到追踪到其中的一部分,即在此函数中的走向,所以希望用到时不了解的朋友可以查看这里的函数代码,这个假设是在你不想一边看这里的文章一边看你手中的源码时。这里应用程序界面函数会能过C函数库到达sys_socket(),我们继续追踪sys_socket()

asmlinkage long sys_socket(int family, int type, int protocol)
{
    int retval;
    struct socket *sock;

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

    retval = sock_map_fd(sock);
    if (retval < 0)
        goto out_release;

out:
    /* It may be already another descriptor 8) Not kernel problem. */
    return retval;

out_release:
    sock_release(sock);
    return retval;
}

这个函数从名称可以看出是为了修建一个插口,对照应用程序界面传递过来的三个参数,第一个参数family应用程序传递过来的值为AF_UNIX或者AF_INET,这里我们声明本节我们重要的针对第一种情况AF_UNIX展开论述。第二个参数是SOCK_STREAM,第三个参数是0。我们从上面的函数中可以看到是进入了sock_create()函数去创建socket。另外在2.6.26内核的net/socket.c处的300行我们可以看到

static struct vfsmount *sock_mnt __read_mostly;

static struct file_system_type sock_fs_type = {
    .name =        "sockfs",
    .get_sb =    sockfs_get_sb,
    .kill_sb =    kill_anon_super,
};

这里如果看过前边共享内存了的第一节的朋友可以看到那里我们也有一个file_system_type,但是这里安装过程与共享内存的文件系统不同,我们说过详细的关于文件系统的学习我会专门有文章进行介绍,这里我们要知道linux内核在系统初始化时会调用一个do_kern_mount()函数,我们暂且贴出这个函数的代码供朋友们浏览,它在fs/super.c的961行处

struct vfsmount *
do_kern_mount(const char *fstype, int flags, const char *name, void *data)
{
    struct file_system_type *type = get_fs_type(fstype);
    struct vfsmount *mnt;
    if (!type)
        return ERR_PTR(-ENODEV);
    mnt = vfs_kern_mount(type, flags, name, data);
    if (!IS_ERR(mnt) && (type->fs_flags & FS_HAS_SUBTYPE) &&
     !mnt->mnt_sb->s_subtype)
        mnt = fs_set_subtype(mnt, fstype);
    put_filesystem(type);
    return mnt;
}

我们这里的 sock_fs_type正是通过这个函数安装的,安装完毕后,做为安装点的sock_mnt保存有文件系统的连接插件。我们暂且介绍这么多,现在关键是看sock_create()函数

int sock_create(int family, int type, int protocol, struct socket **res)
{
    return __sock_create(current->nsproxy->net_ns, family, type, protocol, res, 0);
}

可以看到一转手交给了_sock_create()函数处理,这个函数代码量较大,我分需要分段来看

static int __sock_create(struct net *net, int family, int type, int protocol,
             struct socket **res, int kern)
{
    int err;
    struct socket *sock;
    const struct net_proto_family *pf;

    /*
     * Check protocol is in range
     */

    if (family < 0 || family >= NPROTO)
        return -EAFNOSUPPORT;
    if (type < 0 || type >= SOCK_MAX)
        return -EINVAL;

    /* Compatibility.

     This uglymoron is moved from INET layer to here to avoid
     deadlock in module load.
     */

    if (family == PF_INET && type == SOCK_PACKET) {
        static int warned;
        if (!warned) {
            warned = 1;
            printk(KERN_INFO "%s uses obsolete (PF_INET,SOCK_PACKET)/n",
             current->comm);
        }
        family = PF_PACKET;
    }

    err = security_socket_create(family, type, protocol, kern);
    if (err)
        return err;

这个函数是检测规则域的,因为我们目前讲到过程是根据上面提到的AF_UNIX所以,这段代码只是检测一下传递过来的参数是否在规定的范围,而 security_socket_create我们可以看到是空的只是返回一个0。下一篇我们继续重点讲解关于创建socket.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CSDN IT狂飙上传的代码均可运行,功能ok的情况下才上传的,直接替换数据即可使用,小白也能轻松上手 【资源说明】 基于MATLAB实现的有限差分法实验报告用MATLAB中的有限差分法计算槽内电位;对比解析法和数值法的异同点;选取一点,绘制收敛曲线;总的三维电位图+使用说明文档 1、代码压缩包内容 主函数:main.m; 调用函数:其他m文件;无需运行 运行结果效果图; 2、代码运行版本 Matlab 2020b;若运行有误,根据提示GPT修改;若不会,私信博主(问题描述要详细); 3、运行操作步骤 步骤一:将所有文件放到Matlab的当前文件夹中; 步骤二:双击打开main.m文件; 步骤三:点击运行,等程序运行完得到结果; 4、仿真咨询 如需其他服务,可后台私信博主; 4.1 期刊或参考文献复现 4.2 Matlab程序定制 4.3 科研合作 功率谱估计: 故障诊断分析: 雷达通信:雷达LFM、MIMO、成像、定位、干扰、检测、信号分析、脉冲压缩 滤波估计:SOC估计 目标定位:WSN定位、滤波跟踪、目标定位 生物电信号:肌电信号EMG、脑电信号EEG、心电信号ECG 通信系统:DOA估计、编码译码、变分模态分解、管道泄漏、滤波器、数字信号处理+传输+分析+去噪、数字信号调制、误码率、信号估计、DTMF、信号检测识别融合、LEACH协议、信号检测、水声通信 5、欢迎下载,沟通交流,互相学习,共同进步!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值