socket系统调用

来源:互联网 发布:win10软件全屏就闪屏 编辑:程序博客网 时间:2024/06/05 08:16

从别人那里看,一边翻源码。总结的socket的系统调用,主要是参考大神的


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;
 }
sock_create函数:
 int sock_create(int family, int type, int protocol, struct socket **res)
 {
  /* 传入0表示是用户态进程创建套接口 */
  return __sock_create(family, type, protocol, res, 0);
 }
 
 __sock_create(family, type, protocol, res, 0);
 
 /**
  * 创建一个套接口
  *  family:  套接口协议族
  *  type:  套接口类型
  *  protocol: 传输层协议
  *  res:  输出参数,创建成功的套接口指针
  *  kern:  由内核还是应用程序创建。
  */
 static int __sock_create(int family, int type, int protocol, struct socket **res, int kern)
 {
  int err;
  struct socket *sock;
  /*
   * Check protocol is in range
   */
  if (family < 0 || family >= NPROTO)/* 参数合法性检测 */
   return -EAFNOSUPPORT;
  if (type < 0 || type >= SOCK_MAX)
   return -EINVAL;
  /**
   * IPV4协议族的SOCK_PACKET类型套接口已经不被支持
   * 为兼容旧程序,转换为PF_PACKET */
  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;
   
 #if defined(CONFIG_KMOD)
  /* Attempt to load a protocol module if the find failed.
   *
   * 12/09/1996 Marcin: But! this makes REALLY only sense, if the user
   * requested real, full-featured networking support upon configuration.
   * Otherwise module support will break!
   */
  if (net_families[family]==NULL)/* 相应的协议族在内核中尚不存在,加载模块以支持该协议族 */
  {
   request_module("net-pf-%d",family);
  }
 #endif
  /* 等待,直到锁被释放 */
  net_family_read_lock();
  if (net_families[family] == NULL) {/* 如果协议族仍然不存在,说明不支持此协议族 */
   err = -EAFNOSUPPORT;
   goto out;
  }
 /*
  * 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.
  */
  if (!(sock = sock_alloc())) {/* 分配与inode关联的套接口 */
   printk(KERN_WARNING "socket: no more sockets\n");
   err = -ENFILE;  /* Not exactly a match, but its the
         closest posix thing */
   goto out;
  }
  sock->type  = type;/* 设置套接口类型。 */
  /*
   * We will call the ->create function, that possibly is in a loadable
   * module, so we have to bump that loadable module refcnt first.
   */
  err = -EAFNOSUPPORT;
  if (!try_module_get(net_families[family]->owner))/* 增加对协议族模块的引用,如果失败则退出 */
   goto out_release;
  /* 调用协议族的创建方法,对IPV4来说,调用的是inet_create */
  if ((err = net_families[family]->create(sock, protocol)) < 0)
   goto out_module_put;
  /*
   * Now to bump the refcnt of the [loadable] module that owns this
   * socket at sock_release time we decrement its refcnt.
   */
  if (!try_module_get(sock->ops->owner)) {/* 增加传输层模块的引用计数 */
   sock->ops = NULL;
   goto out_module_put;
  }
  /*
   * Now that we're done with the ->create function, the [loadable]
   * module can have its refcnt decremented
   */
  /* 增加了传输层模块的引用计数后,可以释放协议族的模块引用计数 */
  module_put(net_families[family]->owner);
  *res = sock;
  /* 通知安全模块,对创建过程进行检查。 */
  security_socket_post_create(sock, family, type, protocol, kern);
 out:
  net_family_read_unlock();
  return err;
 out_module_put:
  module_put(net_families[family]->owner);
 out_release:
  sock_release(sock);
  goto out;
 }
sock_create函数申请了一个struct socket结构体sock,又调用pf->create指向的函数对通过sock
进行初始化。分析inet_init等网络子系统初始化函数得知,pf->create指向的是inet_create函数

 /*
  * Create an inet socket.
  */
 /**
  * 创建一个IPV4的socket
  *  sock:  已经创建的套接口
  *  protocol: 套接口的协议号
  */
 static int inet_create(struct socket *sock, int protocol)
 {
  struct sock *sk;
  struct list_head *p;
  struct inet_protosw *answer;
  struct inet_sock *inet;
  struct proto *answer_prot;
  unsigned char answer_flags;
  char answer_no_check;
  int err;
  sock->state = SS_UNCONNECTED;/* 初始化套接口状态 */
  /* Look for the requested type/protocol pair. */
  answer = NULL;
  rcu_read_lock();
  list_for_each_rcu(p, &inetsw[sock->type]) {/* 根据套接口类型遍历IPV4链表  */
   answer = list_entry(p, struct inet_protosw, list);
   /* Check the non-wild match. */
   if (protocol == answer->protocol) {/* 比较传输层协议 */
    if (protocol != IPPROTO_IP)
     break;
   } else {
    /* Check for the two wild cases. */
    if (IPPROTO_IP == protocol) {
     protocol = answer->protocol;
     break;
    }
    if (IPPROTO_IP == answer->protocol)
     break;
   }
   answer = NULL;
  }
  err = -ESOCKTNOSUPPORT;
  if (!answer)/* 找不到对应的传输层协议 */
   goto out_rcu_unlock;
  err = -EPERM;
  /* 创建该类型的套接口需要特定能力,而当前进程没有这种能力,则退出 */
  if (answer->capability > 0 && !capable(answer->capability))
   goto out_rcu_unlock;
  err = -EPROTONOSUPPORT;
  if (!protocol)
   goto out_rcu_unlock;
  /* 设置套接口层的接口 */
  sock->ops = answer->ops;
  answer_prot = answer->prot;
  answer_no_check = answer->no_check;
  answer_flags = answer->flags;
  rcu_read_unlock();
  BUG_TRAP(answer_prot->slab != NULL);
  err = -ENOBUFS;
  /* 根据协议族等参数分配传输控制块。 */
  sk = sk_alloc(PF_INET, GFP_KERNEL,
      answer_prot->slab_obj_size,
      answer_prot->slab);
  if (sk == NULL)
   goto out;
  err = 0;
  sk->sk_prot = answer_prot;
  /* 设置是否需要校验和 */
  sk->sk_no_check = answer_no_check;
  /* 是否可以重用地址和端口 */
  if (INET_PROTOSW_REUSE & answer_flags)
   sk->sk_reuse = 1;
  inet = inet_sk(sk);
  if (SOCK_RAW == sock->type) {/* 是原始套接口 */
   inet->num = protocol;/* 设置本地端口为协议号 */
   if (IPPROTO_RAW == protocol)/* 如果是RAW协议,则需要自己构建IP首部 */
    inet->hdrincl = 1;
  }
  if (ipv4_config.no_pmtu_disc)/* 是否支持PMTU */
   inet->pmtudisc = IP_PMTUDISC_DONT;
  else
   inet->pmtudisc = IP_PMTUDISC_WANT;
  inet->id = 0;
  sock_init_data(sock, sk);/* 初始化传输控制块 */
  sk_set_owner(sk, sk->sk_prot->owner);
  /* 在套接口被释放时,进行一些清理工作。 */
  sk->sk_destruct    = inet_sock_destruct;
  /* 设置协议族和协议号 */
  sk->sk_family    = PF_INET;
  sk->sk_protocol    = protocol;
  /* 设置后备队列接收函数。 */
  sk->sk_backlog_rcv = sk->sk_prot->backlog_rcv;
  inet->uc_ttl = -1;
  inet->mc_loop = 1;
  inet->mc_ttl = 1;
  inet->mc_index = 0;
  inet->mc_list = NULL;
 #ifdef INET_REFCNT_DEBUG
  atomic_inc(&inet_sock_nr);
 #endif
  if (inet->num) {/* 如果设置了本地端口号 */
   /* It assumes that any protocol which allows
    * the user to assign a number at socket
    * creation time automatically
    * shares.
    */
   inet->sport = htons(inet->num);/* 设置网络序的本地端口号 */
   /* Add to protocol hash chains. */
   sk->sk_prot->hash(sk);/* 将传输控制块加入到静列表中 */
  }
  if (sk->sk_prot->init) {/* 调用传输层的初始化回调,对TCP来说,就是tcp_v4_init_sock。UDP没有设置回调 */
   err = sk->sk_prot->init(sk);
   if (err)
    sk_common_release(sk);
  }
 out:
  return err;
 out_rcu_unlock:
  rcu_read_unlock();
  goto out;
 }
经过协议和类型的匹配后,answer->ops指向的是inet_stream_ops,answer->prot指向的是tcp_prot:

/*
分配传输控制块
 */
struct sock *sk_alloc(int family, int priority, int zero_it, kmem_cache_t *slab)
{
 struct sock *sk = NULL;
 if (!slab)/* 如果传输层没有指定缓存分配区,则默认使用sk_cachep */
  slab = sk_cachep;
 /**
  * 在指定的分配区中,用指定的分配参数分配传输控制块。
  */
 sk = kmem_cache_alloc(slab, priority);
 if (sk) {
  if (zero_it) {/* 需要初始化它 */
   memset(sk, 0,
          zero_it == 1 ? sizeof(struct sock) : zero_it);
   sk->sk_family = family;
   sock_lock_init(sk);
  }
  /* 释放时需要使用 */
  sk->sk_slab = slab;
  
  if (security_sk_alloc(sk, family, priority)) {/* 安全审计,如果失败则释放传输层接口 */
   kmem_cache_free(slab, sk);
   sk = NULL;
  }
 }
 return sk;
}
sock_init_data函数:
 void sock_init_data(struct socket *sock, struct sock *sk)
 {
  skb_queue_head_init(&sk->sk_receive_queue);
  skb_queue_head_init(&sk->sk_write_queue);
  skb_queue_head_init(&sk->sk_error_queue);
  sk->sk_send_head = NULL;
  init_timer(&sk->sk_timer);
  
  sk->sk_allocation = GFP_KERNEL;
  sk->sk_rcvbuf  = sysctl_rmem_default;
  sk->sk_sndbuf  = sysctl_wmem_default;
  sk->sk_state  = TCP_CLOSE;
  sk->sk_zapped  = 1;
  sk->sk_socket  = sock;
  if(sock)
  {
   sk->sk_type = sock->type;
   sk->sk_sleep = &sock->wait;
   sock->sk = sk;
  } else
   sk->sk_sleep = NULL;
  rwlock_init(&sk->sk_dst_lock);
  rwlock_init(&sk->sk_callback_lock);
  sk->sk_state_change = sock_def_wakeup;
  sk->sk_data_ready = sock_def_readable;
  sk->sk_write_space = sock_def_write_space;
  sk->sk_error_report = sock_def_error_report;
  sk->sk_destruct  = sock_def_destruct;
  sk->sk_sndmsg_page = NULL;
  sk->sk_sndmsg_off = 0;
  sk->sk_peercred.pid  = 0;
  sk->sk_peercred.uid = -1;
  sk->sk_peercred.gid = -1;
  sk->sk_write_pending = 0;
  sk->sk_rcvlowat  = 1;
  sk->sk_rcvtimeo  = MAX_SCHEDULE_TIMEOUT;
  sk->sk_sndtimeo  = MAX_SCHEDULE_TIMEOUT;
  sk->sk_owner  = NULL;
  sk->sk_stamp.tv_sec     = -1L;
  sk->sk_stamp.tv_usec    = -1L;
  atomic_set(&sk->sk_refcnt, 1);
 }
 
sock_create函数的功能:创建一个struct socket类型的变量sock,找到TCP对应的操作
函数集inet_stream_ops并安装到sock->ops中;再申请一个struct tcp_sock结构体大小
的内存并强制赋给struct sock指针sk,使sk->prot指向tcp_prot,将sk赋给sock->sk使
得sock与sk关联起来,并对sk和sock进行初始化。而sock->ops和sk->prot的指向,决定
了后续所有系统调用的行为集。
sock_map_fd函数:
 /**
  * 将套接口与文件描述符绑定。
  */
 int sock_map_fd(struct socket *sock)
 {
  int fd;
  struct qstr this;
  char name[32];
  /*
   * Find a file descriptor suitable for return to the user.
   */
  /**
   * 获得空闲的文件描述符。
   */
  fd = get_unused_fd();
  if (fd >= 0) {/* 成功分配文件描述符 */
   struct file *file = get_empty_filp();
   if (!file) {
    put_unused_fd(fd);
    fd = -ENFILE;
    goto out;
   }
   sprintf(name, "[%lu]", SOCK_INODE(sock)->i_ino);
   this.name = name;
   this.len = strlen(name);
   this.hash = SOCK_INODE(sock)->i_ino;
   /**
    * 分配文件目录项。
    */
   file->f_dentry = d_alloc(sock_mnt->mnt_sb->s_root, &this);
   if (!file->f_dentry) {
    put_filp(file);
    put_unused_fd(fd);
    fd = -ENOMEM;
    goto out;
   }
   file->f_dentry->d_op = &sockfs_dentry_operations;
   d_add(file->f_dentry, SOCK_INODE(sock));
   file->f_vfsmnt = mntget(sock_mnt);
   file->f_mapping = file->f_dentry->d_inode->i_mapping;
   sock->file = file;
   file->f_op = SOCK_INODE(sock)->i_fop = &socket_file_ops;
   file->f_mode = FMODE_READ | FMODE_WRITE;
   file->f_flags = O_RDWR;
   file->f_pos = 0;
   /* 将文件描述符实例增加到已经打开的文件列表中,完成文件与进程的绑定 */
   fd_install(fd, file);
  }
 out:
  return fd;
 }
 
可见sock_map_fd函数的功能是:获取一个未被使用的整数fd,申请一个文件结构体对象newfile,将fd与newfile关联起来。
void fastcall fd_install(unsigned int fd, struct file * file)
{
 struct files_struct *files = current->files;
 spin_lock(&files->file_lock);
 if (unlikely(files->fd[fd] != NULL))
  BUG();
 files->fd[fd] = file;
 spin_unlock(&files->file_lock);
}
对文件的读写等操作由文件系统inode的i_fop指向的函数集实现,在socket结构体中这个函数集是socket_file_ops:
/**
 * 套接口文件的接口
 */
static struct file_operations socket_file_ops = {
 .owner = THIS_MODULE,
 .llseek = no_llseek,
 .aio_read = sock_aio_read,
 .aio_write = sock_aio_write,
 .poll =  sock_poll,
 .unlocked_ioctl = sock_ioctl,
 .mmap =  sock_mmap,
 .open =  sock_no_open, /* special open code to disallow open via /proc */
 .release = sock_close,
 .fasync = sock_fasync,
 .readv = sock_readv,
 .writev = sock_writev,
 .sendpage = sock_sendpage
};

sock_map_fd函数的功能了:将socke结构体对象与文件系统结构关联起来,
使得socket成为了一种特殊的文件系统;并生成一个文件描述符,将描述
符与socket文件系统关联,使得Linux API接口可以通过文件描述符操作socket。
将socket与文件系统关联起来是符合Unix系统“Everything is a file”的
理念的。但这样做有什么好处呢?我能想到的好处有两点:一是在socket上
产生的事件可以和其它类型文件系统的事件一起交由poll或epoll这类高效
的事件监控机制管理,这样可以大大扩展socket的应用范围;二是进程无
论是正常退出还是异常退出,内核在销毁进程的资源时都会关闭其打开的
所有文件描述符,这样即使进程被异常杀死,也会触发close系统调用将
socket对应的连接关掉(如果是TCP连接的话会通知对端关闭连接),这就
使得异常情况下网络连接的资源也能及时得到回收。
总结一下socket系统调用的功能:
 在内核生成一个socket和tcp_sock类型的
 对象用于保存TCP连接信息,并安装TCP相关的操作函数集以限定后续的系统
 调用的行为;将socket对象与一个file类型的对象绑定到一起,并将一个文
 件描述符与file对象绑定到一起,最后将这个文件描述符返回给调用者。

原创粉丝点击