激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - 編程技術 - 從內核看文件描述符傳遞的實現

從內核看文件描述符傳遞的實現

2021-06-18 23:25編程雜技theanarkh 編程技術

文件描述符是內核提供的一個非常有用的技術,典型的在服務器中,主進程負責接收請求,然后把請求傳遞給子進程處理。本文分析在內核中,文件描述符傳遞是如何實現的。

從內核看文件描述符傳遞的實現

文件描述符是內核提供的一個非常有用的技術,典型的在服務器中,主進程負責接收請求,然后把請求傳遞給子進程處理。本文分析在內核中,文件描述符傳遞是如何實現的。

我們先看一下文件描述符傳遞到底是什么概念。假設主進程打開了一個文件,我們看看進程和文件系統的關系。

從內核看文件描述符傳遞的實現

如果這時候主進程fork出一個子進程,那么架構如下。

從內核看文件描述符傳遞的實現

我們看到主進程和子進程都指向同一個文件。那么如果這時候住進程又打開了一個文件。架構如下。

從內核看文件描述符傳遞的實現

我們看到新打開的文件,子進程是不會再指向了的。假設文件底層的資源是TCP連接,而主進程想把這個關系同步到子進程中,即交給子進程處理,那怎么辦呢?這時候就需要用到文件描述符傳遞。下面是我們期待的架構。

從內核看文件描述符傳遞的實現

通常主進程會close掉對應的文件描述符,即解除引用關系。不過這里我們可以不關注這個。文件描述符這種能力不是天然,需要內核支持,如果我們單純把fd(文件描述符)當作數據傳給子進程,子進程無法指向對應的文件的。下面我們如何使用這個技術并通過內核來看看如何實現這個技術。下面使用代碼摘自Libuv。

  1. int fd_to_send; 
  2.     // 核心數據結構 
  3.     struct msghdr msg; 
  4.     struct cmsghdr *cmsg; 
  5.     union { 
  6.       char data[64]; 
  7.       struct cmsghdr alias; 
  8.     } scratch; 
  9.     // 獲取需要發送的文件描述符 
  10.     fd_to_send = uv__handle_fd((uv_handle_t*) req->send_handle); 
  11.  
  12.     memset(&scratch, 0, sizeof(scratch)); 
  13.  
  14.     msg.msg_name = NULL
  15.     msg.msg_namelen = 0; 
  16.     msg.msg_iov = iov; 
  17.     msg.msg_iovlen = iovcnt; 
  18.     msg.msg_flags = 0; 
  19.  
  20.     msg.msg_control = &scratch.alias; 
  21.     msg.msg_controllen = CMSG_SPACE(sizeof(fd_to_send)); 
  22.  
  23.     cmsg = CMSG_FIRSTHDR(&msg); 
  24.     cmsg->cmsg_level = SOL_SOCKET; 
  25.     cmsg->cmsg_type = SCM_RIGHTS; 
  26.     cmsg->cmsg_len = CMSG_LEN(sizeof(fd_to_send)); 
  27.  
  28.     /* silence aliasing warning */ 
  29.     { 
  30.       void* pv = CMSG_DATA(cmsg); 
  31.       int* pi = pv; 
  32.       *pi = fd_to_send; 
  33.     } 
  34.     // fd是Unix域對應的文件描述符 
  35.     int fd = uv__stream_fd(stream); 
  36.     // 發送文件描述符 
  37.     sendmsg(fd, &msg, 0); 

我們看到發送文件描述符是比較復雜的,使用的主要數據結構是msghdr。把需要發送的文件描述符保存到msghdr中,并設置一些標記。然后通過Unix域發送(Unix是唯一一種支持文件描述符傳遞的進程間通信方式)。我們下來主要來分析內核對sendmsg的實現。

  1. case SYS_SENDMSG: 
  2.         err = __sys_sendmsg(a0, (struct user_msghdr __user *)a1, 
  3.                     a[2], true); 

該系統調用對應的是__sys_sendmsg。

  1. long __sys_sendmsg(int fd, struct user_msghdr __user *msg, unsigned int flags, 
  2.            bool forbid_cmsg_compat){ 
  3.     int fput_needed, err; 
  4.     struct msghdr msg_sys; 
  5.     struct socket *sock; 
  6.     // 根據fd找到socket 
  7.     sock = sockfd_lookup_light(fd, &err, &fput_needed); 
  8.     ___sys_sendmsg(sock, msg, &msg_sys, flags, NULL, 0); 

后面的鏈路很長syssendmsg->__sys_sendmsg->sock_sendmsg->sock_sendmsg_nosec。

  1. static inline int sock_sendmsg_nosec(struct socket *sock, struct msghdr *msg){ 
  2.     int ret = INDIRECT_CALL_INET(sock->ops->sendmsg, inet6_sendmsg, 
  3.                      inet_sendmsg, sock, msg, 
  4.                      msg_data_left(msg)); 
  5.     BUG_ON(ret == -EIOCBQUEUED); 
  6.     return ret; 

我們看到最后調用sock->ops->sendmsg,我們看看Unix域對sendmsg的實現。Unix域有SOCK_STREAM和SOCK_DGRAM兩種模式,我們選第一個分析就可以。

  1. static int unix_stream_sendmsg(struct socket *sock, struct msghdr *msg, 
  2.                    size_t len){ 
  3.     struct sock *sk = sock->sk; 
  4.     struct sock *other = NULL
  5.     int err, size
  6.     struct sk_buff *skb; 
  7.     int sent = 0; 
  8.     struct scm_cookie scm; 
  9.     bool fds_sent = false
  10.     int data_len; 
  11.     // 把文件描述符信息復制到scm 
  12.     scm_send(sock, msg, &scm, false); 
  13.     // 通信的對端 
  14.     other = unix_peer(sk); 
  15.     // 不斷構建數據包skbuff發送,直到發送完畢 
  16.     while (sent < len) { 
  17.         // 分配一個sdk承載消息 
  18.         skb = sock_alloc_send_pskb(sk, size - data_len, data_len, 
  19.                        msg->msg_flags & MSG_DONTWAIT, &err, 
  20.                        get_order(UNIX_SKB_FRAGS_SZ)); 
  21.         // 把scm復制到skb               
  22.         err = unix_scm_to_skb(&scm, skb, !fds_sent); 
  23.         // 把數據寫到skb 
  24.         err = skb_copy_datagram_from_iter(skb, 0, &msg->msg_iter, size); 
  25.         // 插入對端的消息隊列 
  26.         skb_queue_tail(&other->sk_receive_queue, skb); 
  27.         // 通知對端有數據可讀 
  28.         other->sk_data_ready(other); 
  29.         sent += size
  30.     } 
  31.     // ... 

我們看到,整體的邏輯不負責,主要是根據數據構造skb結構體,然后插入對端消息隊列,最后通知對端有消息可讀,我們這里只關注文件描述符的處理。首先我們看看scm_send。

  1. static __inline__ int scm_send(struct socket *sock, struct msghdr *msg, 
  2.                    struct scm_cookie *scm, bool forcecreds){ 
  3.     memset(scm, 0, sizeof(*scm)); 
  4.     scm->creds.uid = INVALID_UID; 
  5.     scm->creds.gid = INVALID_GID; 
  6.     unix_get_peersec_dgram(sock, scm); 
  7.     if (msg->msg_controllen <= 0) 
  8.         return 0; 
  9.     return __scm_send(sock, msg, scm);}int __scm_send(struct socket *sock, struct msghdr *msg, struct scm_cookie *p){ 
  10.     struct cmsghdr *cmsg; 
  11.     int err; 
  12.  
  13.     for_each_cmsghdr(cmsg, msg) { 
  14.         switch (cmsg->cmsg_type) 
  15.         { 
  16.         case SCM_RIGHTS: 
  17.             err=scm_fp_copy(cmsg, &p->fp); 
  18.             if (err<0) 
  19.                 goto error; 
  20.             break; 
  21.         } 
  22.     } 

我們看到__scm_send遍歷待發送的數據,然后判斷cmsg->cmsg_type的值,我們這里是SCM_RIGHTS(見最開始的使用代碼),接著調用scm_fp_copy。

  1. static int scm_fp_copy(struct cmsghdr *cmsg, struct scm_fp_list **fplp){ 
  2.     int *fdp = (int*)CMSG_DATA(cmsg); 
  3.     struct scm_fp_list *fpl = *fplp; 
  4.     struct file **fpp; 
  5.     int i, num; 
  6.  
  7.     num = (cmsg->cmsg_len - sizeof(struct cmsghdr))/sizeof(int); 
  8.     if (!fpl) 
  9.     { 
  10.         fpl = kmalloc(sizeof(struct scm_fp_list), GFP_KERNEL); 
  11.         *fplp = fpl; 
  12.         fpl->count = 0; 
  13.         fpl->max = SCM_MAX_FD; 
  14.         fpl->user = NULL
  15.     } 
  16.     fpp = &fpl->fp[fpl->count]; 
  17.     // 遍歷然后把fd對應的file保存到fpp中。 
  18.     for (i=0; i< num; i++) 
  19.     { 
  20.         int fd = fdp[i]; 
  21.         struct file *file; 
  22.  
  23.         if (fd < 0 || !(file = fget_raw(fd))) 
  24.             return -EBADF; 
  25.         *fpp++ = file; 
  26.         fpl->count++; 
  27.     } 
  28.  
  29.     if (!fpl->user
  30.         fpl->user = get_uid(current_user()); 
  31.  
  32.     return num; 

我們看到scm_fp_copy遍歷然后把fd對應的file保存到fpp中。而fpp屬于fpl屬于fplp屬于最開始的struct scm_cookie scm(unix_stream_sendmsg函數),即最后把fd對應的file保存到了scm中。接著我們回到unix_stream_sendmsg看unix_scm_to_skb。

  1. static int unix_scm_to_skb(struct scm_cookie *scm, struct sk_buff *skb, bool send_fds){ 
  2.     int err = 0; 
  3.  
  4.     UNIXCB(skb).pid  = get_pid(scm->pid); 
  5.     UNIXCB(skb).uid = scm->creds.uid; 
  6.     UNIXCB(skb).gid = scm->creds.gid; 
  7.     UNIXCB(skb).fp = NULL
  8.     unix_get_secdata(scm, skb); 
  9.     if (scm->fp && send_fds) 
  10.         // 寫到skb 
  11.         err = unix_attach_fds(scm, skb); 
  12.  
  13.     skb->destructor = unix_destruct_scm; 
  14.     return err; 

接著看unix_attach_fds。

  1. int unix_attach_fds(struct scm_cookie *scm, struct sk_buff *skb){ 
  2.     int i; 
  3.     // 復制到skb 
  4.     UNIXCB(skb).fp = scm_fp_dup(scm->fp); 
  5.     return 0;}struct scm_fp_list *scm_fp_dup(struct scm_fp_list *fpl){ 
  6.     struct scm_fp_list *new_fpl; 
  7.     int i; 
  8.  
  9.     new_fpl = kmemdup(fpl, offsetof(struct scm_fp_list, fp[fpl->count]), 
  10.               GFP_KERNEL); 
  11.     if (new_fpl) { 
  12.         for (i = 0; i < fpl->count; i++) 
  13.             get_file(fpl->fp[i]); 
  14.         new_fpl->max = new_fpl->count
  15.         new_fpl->user = get_uid(fpl->user); 
  16.     } 
  17.     return new_fpl; 

至此我們看到數據和文件描述符都寫到了skb中,并插入了對端的消息隊列。我們接著分析對端時如何處理的。我們從recvmsg函數開始,對應Uinix域的實現時unix_stream_recvmsg。

  1. static int unix_stream_recvmsg(struct socket *sock, struct msghdr *msg, 
  2.                    size_t sizeint flags){ 
  3.     struct unix_stream_read_state state = { 
  4.         .recv_actor = unix_stream_read_actor, 
  5.         .socket = sock, 
  6.         .msg = msg, 
  7.         .size = size
  8.         .flags = flags 
  9.     }; 
  10.  
  11.     return unix_stream_read_generic(&state, true); 

接著看

  1. static int unix_stream_read_generic(struct unix_stream_read_state *state, 
  2.                     bool freezable){ 
  3.     struct scm_cookie scm; 
  4.     struct socket *sock = state->socket; 
  5.     struct sock *sk = sock->sk; 
  6.     struct unix_sock *u = unix_sk(sk); 
  7.  
  8.     // 拿到一個skb 
  9.     skb = skb_peek(&sk->sk_receive_queue); 
  10.     // 出隊 
  11.     skb_unlink(skb, &sk->sk_receive_queue); 
  12.     // 復制skb數據到state->msg 
  13.     state->recv_actor(skb, skip, chunk, state); 
  14.     // 處理文件描述符 
  15.     if (UNIXCB(skb).fp) { 
  16.         scm_stat_del(sk, skb); 
  17.         // 復制skb的文件描述符信息到scm 
  18.         unix_detach_fds(&scm, skb); 
  19.     } 
  20.     if (state->msg) 
  21.         scm_recv(sock, state->msg, &scm, flags); 

內核首先通過鉤子函數recv_actor把skb數據數據復制到state->msg,recv_actor對應函數是unix_stream_read_actor。

  1. static int unix_stream_read_actor(struct sk_buff *skb, 
  2.                   int skip, int chunk, 
  3.                   struct unix_stream_read_state *state){ 
  4.     int ret; 
  5.  
  6.     ret = skb_copy_datagram_msg(skb, UNIXCB(skb).consumed + skip, 
  7.                     state->msg, chunk); 
  8.     return ret ?: chunk; 

接著看unix_detach_fds。

  1. void unix_detach_fds(struct scm_cookie *scm, struct sk_buff *skb){ 
  2.     int i; 
  3.     // 寫到scm中 
  4.     scm->fp = UNIXCB(skb).fp; 
  5.     UNIXCB(skb).fp = NULL

unix_detach_fds把文件描述符信息寫到scm。最后調用scm_recv處理文件描述符。

  1. static __inline__ void scm_recv(struct socket *sock, struct msghdr *msg, 
  2.                 struct scm_cookie *scm, int flags){ 
  3.     scm_detach_fds(msg, scm);}void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm){ 
  4.     int fdmax = min_t(int, scm_max_fds(msg), scm->fp->count); 
  5.     int i; 
  6.  
  7.     for (i = 0; i < fdmax; i++) { 
  8.         err = receive_fd_user(scm->fp->fp[i], cmsg_data + i, o_flags); 
  9.         if (err < 0) 
  10.             break; 
  11.     } 

scm_detach_fds中調用了receive_fd_user處理一個文件描述符。其中第一個入參scm->fp->fp[i]是file結構體,即需傳遞到文件描述符對應的file。我們看看怎么處理的。

  1. static inline int receive_fd_user(struct file *file, int __user *ufd, 
  2.                   unsigned int o_flags){ 
  3.     return __receive_fd(-1, file, ufd, o_flags);}int __receive_fd(int fd, struct file *file, int __user *ufd, unsigned int o_flags){ 
  4.     int new_fd; 
  5.     // fd是-1,申請一個新的 
  6.     if (fd < 0) { 
  7.         new_fd = get_unused_fd_flags(o_flags); 
  8.     } else { 
  9.         new_fd = fd; 
  10.     } 
  11.  
  12.     if (fd < 0) { 
  13.         fd_install(new_fd, get_file(file)); 
  14.     } else { 
  15.         // ... 
  16.     } 
  17.     return new_fd; 

我們看到首先申請了當前進程的一個新的文件描述符。然后調用fd_install關聯到file。

  1. void fd_install(unsigned int fd, struct file *file){    
  2.     // current->files是當前進程打開的文件描述符列表 
  3.     __fd_install(current->files, fd, file);}void __fd_install(struct files_struct *files, unsigned int fd, 
  4.         struct file *file){ 
  5.     struct fdtable *fdt; 
  6.     // 拿到管理文件描述符的數據結構 
  7.     fdt = rcu_dereference_sched(files->fdt); 
  8.     // 給某個元素賦值指向file 
  9.     rcu_assign_pointer(fdt->fd[fd], file); 

最后形成下圖所示的架構。

從內核看文件描述符傳遞的實現

后記,我們看到文件描述符傳遞的核心就是在發送的數據中記錄要傳遞的文件描述符對應的file結構體,然后發送做好標記,接著接收的過程中,重新建立新的fd到傳遞的file的關聯關系。

原文鏈接:https://mp.weixin.qq.com/s/mTAfZqC1muk1yEUVcsxj2g

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 国产在线一区二区三区 | 国产一区二区三区四区五区在线 | 一级做受大片免费视频 | 国内一区 | 4p一女两男做爰在线观看 | 久草网在线 | 国产一级91 | 亚洲精品永久视频 | 欧美日韩在线视频一区 | 91久久91久久精品免观看 | 9999久久久久久 | 日本aaaa片毛片免费观看视频 | 国产精品高清一区 | 国产女同玩人妖 | 免费a级观看 | 免费日韩片 | 一级影片在线观看 | 亚洲第一成人在线观看 | 手机国产乱子伦精品视频 | 欧洲成人一区 | 欧美视频一区二区三区四区 | 99亚洲视频 | 日韩中文字幕一区二区三区 | 操穴视频 | 久久免费视频1 | 在线成人一区二区 | 99久久自偷自偷国产精品不卡 | 免费黄色短视频网站 | 最新毛片在线观看 | 91精品国产综合久久久动漫日韩 | 爱性久久久久久久 | 亚洲第一页夜 | 中文字幕国产亚洲 | 国产人成精品综合欧美成人 | 亚洲综合视频在线播放 | 国产欧美精品一区二区三区四区 | 天天看逼 | a级黄色片视频 | 污污黄 | 日本在线播放一区二区 | 羞羞的视频免费 |