自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

從內(nèi)核看文件描述符傳遞的實現(xiàn)

系統(tǒng)
文件描述符是內(nèi)核提供的一個非常有用的技術(shù),典型的在服務(wù)器中,主進(jìn)程負(fù)責(zé)接收請求,然后把請求傳遞給子進(jìn)程處理。本文分析在內(nèi)核中,文件描述符傳遞是如何實現(xiàn)的。

[[406240]]

文件描述符是內(nèi)核提供的一個非常有用的技術(shù),典型的在服務(wù)器中,主進(jìn)程負(fù)責(zé)接收請求,然后把請求傳遞給子進(jìn)程處理。本文分析在內(nèi)核中,文件描述符傳遞是如何實現(xiàn)的。

我們先看一下文件描述符傳遞到底是什么概念。假設(shè)主進(jìn)程打開了一個文件,我們看看進(jìn)程和文件系統(tǒng)的關(guān)系。

如果這時候主進(jìn)程fork出一個子進(jìn)程,那么架構(gòu)如下。

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

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

通常主進(jìn)程會close掉對應(yīng)的文件描述符,即解除引用關(guān)系。不過這里我們可以不關(guān)注這個。文件描述符這種能力不是天然,需要內(nèi)核支持,如果我們單純把fd(文件描述符)當(dāng)作數(shù)據(jù)傳給子進(jìn)程,子進(jìn)程無法指向?qū)?yīng)的文件的。下面我們?nèi)绾问褂眠@個技術(shù)并通過內(nèi)核來看看如何實現(xiàn)這個技術(shù)。下面使用代碼摘自Libuv。

  1. int fd_to_send; 
  2.     // 核心數(shù)據(jù)結(jié)構(gòu) 
  3.     struct msghdr msg; 
  4.     struct cmsghdr *cmsg; 
  5.     union { 
  6.       char data[64]; 
  7.       struct cmsghdr alias; 
  8.     } scratch; 
  9.     // 獲取需要發(fā)送的文件描述符 
  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域?qū)?yīng)的文件描述符 
  35.     int fd = uv__stream_fd(stream); 
  36.     // 發(fā)送文件描述符 
  37.     sendmsg(fd, &msg, 0); 

我們看到發(fā)送文件描述符是比較復(fù)雜的,使用的主要數(shù)據(jù)結(jié)構(gòu)是msghdr。把需要發(fā)送的文件描述符保存到msghdr中,并設(shè)置一些標(biāo)記。然后通過Unix域發(fā)送(Unix是唯一一種支持文件描述符傳遞的進(jìn)程間通信方式)。我們下來主要來分析內(nèi)核對sendmsg的實現(xiàn)。

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

該系統(tǒng)調(diào)用對應(yīng)的是__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.     // 根據(jù)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; 

我們看到最后調(diào)用sock->ops->sendmsg,我們看看Unix域?qū)endmsg的實現(xiàn)。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.     // 把文件描述符信息復(fù)制到scm 
  12.     scm_send(sock, msg, &scm, false); 
  13.     // 通信的對端 
  14.     other = unix_peer(sk); 
  15.     // 不斷構(gòu)建數(shù)據(jù)包skbuff發(fā)送,直到發(fā)送完畢 
  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復(fù)制到skb               
  22.         err = unix_scm_to_skb(&scm, skb, !fds_sent); 
  23.         // 把數(shù)據(jù)寫到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.         // 通知對端有數(shù)據(jù)可讀 
  28.         other->sk_data_ready(other); 
  29.         sent += size
  30.     } 
  31.     // ... 

我們看到,整體的邏輯不負(fù)責(zé),主要是根據(jù)數(shù)據(jù)構(gòu)造skb結(jié)構(gòu)體,然后插入對端消息隊列,最后通知對端有消息可讀,我們這里只關(guān)注文件描述符的處理。首先我們看看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遍歷待發(fā)送的數(shù)據(jù),然后判斷cmsg->cmsg_type的值,我們這里是SCM_RIGHTS(見最開始的使用代碼),接著調(diào)用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對應(yīng)的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對應(yīng)的file保存到fpp中。而fpp屬于fpl屬于fplp屬于最開始的struct scm_cookie scm(unix_stream_sendmsg函數(shù)),即最后把fd對應(yīng)的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.     // 復(fù)制到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; 

至此我們看到數(shù)據(jù)和文件描述符都寫到了skb中,并插入了對端的消息隊列。我們接著分析對端時如何處理的。我們從recvmsg函數(shù)開始,對應(yīng)Uinix域的實現(xiàn)時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.     // 復(fù)制skb數(shù)據(jù)到state->msg 
  13.     state->recv_actor(skb, skip, chunk, state); 
  14.     // 處理文件描述符 
  15.     if (UNIXCB(skb).fp) { 
  16.         scm_stat_del(sk, skb); 
  17.         // 復(fù)制skb的文件描述符信息到scm 
  18.         unix_detach_fds(&scm, skb); 
  19.     } 
  20.     if (state->msg) 
  21.         scm_recv(sock, state->msg, &scm, flags); 

內(nèi)核首先通過鉤子函數(shù)recv_actor把skb數(shù)據(jù)數(shù)據(jù)復(fù)制到state->msg,recv_actor對應(yīng)函數(shù)是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。最后調(diào)用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中調(diào)用了receive_fd_user處理一個文件描述符。其中第一個入?yún)cm->fp->fp[i]是file結(jié)構(gòu)體,即需傳遞到文件描述符對應(yīng)的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; 

我們看到首先申請了當(dāng)前進(jìn)程的一個新的文件描述符。然后調(diào)用fd_install關(guān)聯(lián)到file。

  1. void fd_install(unsigned int fd, struct file *file){    
  2.     // current->files是當(dāng)前進(jìn)程打開的文件描述符列表 
  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.     // 拿到管理文件描述符的數(shù)據(jù)結(jié)構(gòu) 
  7.     fdt = rcu_dereference_sched(files->fdt); 
  8.     // 給某個元素賦值指向file 
  9.     rcu_assign_pointer(fdt->fd[fd], file); 

最后形成下圖所示的架構(gòu)。

后記,我們看到文件描述符傳遞的核心就是在發(fā)送的數(shù)據(jù)中記錄要傳遞的文件描述符對應(yīng)的file結(jié)構(gòu)體,然后發(fā)送做好標(biāo)記,接著接收的過程中,重新建立新的fd到傳遞的file的關(guān)聯(lián)關(guān)系。

 

責(zé)任編輯:武曉燕 來源: 編程雜技
相關(guān)推薦

2025-01-10 15:13:38

2019-03-05 22:15:08

BashLinux命令

2012-08-08 10:31:41

IBMdW

2023-12-13 14:01:34

Elasticsea文件描述符操作系統(tǒng)

2020-02-07 18:16:01

進(jìn)程線程底層原理

2021-06-26 07:04:24

Epoll服務(wù)器機制

2019-07-05 14:20:45

RPC服務(wù)器模型

2017-02-05 10:06:53

Python黑魔法描述符

2009-07-08 09:46:45

Servlet注釋部署描述符

2016-10-28 21:55:28

Javascript屬性特性屬性描述符

2023-04-06 15:22:15

Linux進(jìn)程系統(tǒng)

2021-05-19 14:48:58

Linux文件fd

2009-09-04 14:04:53

C#文檔

2021-07-07 23:38:05

內(nèi)核IOLinux

2019-07-09 15:30:31

Linuxulimit文件描述符

2019-07-09 14:30:16

LinuxJava 服務(wù)器

2016-08-23 17:21:51

UnixLinux重定向

2016-09-20 15:21:35

LinuxInnoDBMysql

2019-03-27 09:14:38

CPU內(nèi)核應(yīng)用程序

2025-04-21 03:03:00

點贊
收藏

51CTO技術(shù)棧公眾號