這是一個想要從 userspace 一直深入到 driver,理解 Linux packet 究竟如何運行的流程圖。
我在交大做的東西都在 driver space,使用 ath9k/ath10k 做實驗。我所理解的是 ath9k 上面對著 mac80211 (Linux 的 softmac),也就是說,driver 在功能上,只需要實做struct ieee80211_ops
的功能即可。主要都在處理 .wake_tx_queue
的功能。
對於他之上,頂多知道 ieee80211_queue_skb 最後會 call drv_wake_tx_queue 然後根據 driver 不同呼叫不同 driver 的實做,以 ath9k 而言就是 ath9k_wake_tx_queue。
還是想要知道一下,究竟這個 packet 是如何被傳入 mac80211 這層,順便解答一個以前問自己的問題「wireshark 抓封包時究竟是從那一層抓的」。
使用兩種工具,一個是 cflow、一個是 pycflow2dot。前者用來描述 call graph,後者拿來轉成 graphviz 的 dot 語言並且輸出 svg。兩者都有做修改,cflow 的部份請參考備註的部份,pycflow2dot 請參考我修改的版本: mlouielu/pycflow2dot。
從上面這張圖我們可以看到,在 sock_sendmsg_nosec 這個 function 最後呼叫了 sock->ops->sendmsg(sock, msg, msg_data_left(msg));
,這裡是一個典型的 Linux kernel Object Oriented 的做法「method dispatch」。
1 2 3 4 5 6 |
static inline int sock_sendmsg_nosec(struct socket *sock, struct msghdr *msg) { int ret = sock->ops->sendmsg(sock, msg, msg_data_left(msg)); BUG_ON(ret == –EIOCBQUEUED); return ret; } |
sock->ops
是 struct proto_ops
的物件,在裡面定義了許多的 method,而其他人只要實做其中的 method,就可以在通用的界面上被使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
/** * struct socket – general BSD socket * @state: socket state (%SS_CONNECTED, etc) * @type: socket type (%SOCK_STREAM, etc) * @flags: socket flags (%SOCK_NOSPACE, etc) * @ops: protocol specific socket operations * @file: File back pointer for gc * @sk: internal networking protocol agnostic socket representation * @wq: wait queue for several uses */ struct socket { socket_state state; short type; unsigned long flags; struct socket_wq *wq; struct file *file; struct sock *sk; const struct proto_ops *ops; }; |
在程式碼 中搜尋有實做 struct proto_ops
的檔案
1 2 3 |
./ipv4/af_inet.c:const struct proto_ops inet_stream_ops = { ./ipv4/af_inet.c:const struct proto_ops inet_dgram_ops = { ./ipv4/af_inet.c:static const struct proto_ops inet_sockraw_ops = { |
確認是在 net/ipv4/af_inet.c
中實做了 stream (TCP) / dgram (UDP) / raw socket 的 struct proto_ops
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
const struct proto_ops inet_stream_ops = { .family = PF_INET, .owner = THIS_MODULE, .release = inet_release, .bind = inet_bind, .connect = inet_stream_connect, .socketpair = sock_no_socketpair, .accept = inet_accept, .getname = inet_getname, .poll = tcp_poll, .ioctl = inet_ioctl, .listen = inet_listen, .shutdown = inet_shutdown, .setsockopt = sock_common_setsockopt, .getsockopt = sock_common_getsockopt, .sendmsg = inet_sendmsg, .recvmsg = inet_recvmsg, #ifdef CONFIG_MMU .mmap = tcp_mmap, #endif .sendpage = inet_sendpage, .splice_read = tcp_splice_read, .read_sock = tcp_read_sock, .sendmsg_locked = tcp_sendmsg_locked, .sendpage_locked = tcp_sendpage_locked, .peek_len = tcp_peek_len, #ifdef CONFIG_COMPAT .compat_setsockopt = compat_sock_common_setsockopt, .compat_getsockopt = compat_sock_common_getsockopt, .compat_ioctl = inet_compat_ioctl, #endif .set_rcvlowat = tcp_set_rcvlowat, }; EXPORT_SYMBOL(inet_stream_ops); |
這邊可以看到,.sendmsg = inet_sendmsg
,也就是說前面的 sock_sendmsg_nosec 呼叫 sock->ops->sendmsg
如果是 TCP 封包的話,就會使用 inet_sendmsg
作為呼叫的 function。從 inet_sendmsg 開始我們可以繼續追蹤下去。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
sk_prot->sendmsg“ >int inet_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) { struct sock *sk = sock->sk; sock_rps_record_flow(sk); /* We may need to bind the socket. */ if (!inet_sk(sk)->inet_num && !sk->sk_prot->no_autobind && inet_autobind(sk)) return –EAGAIN; return sk->sk_prot->sendmsg(sk, msg, size); } EXPORT_SYMBOL(inet_sendmsg); |
實際上,如果你有看 code 的話,會發現 TCP/UDP/RAW 都是呼叫 inet_sendmsg,這不太合理,因為至少我們知道 TCP 跟 UDP 的運作機制是不同的。我們可以看到,最後呼叫的是 sk->sk_prot->sendmsg
,也就是 struct sock
裡面的 ops,因此,各個不同 protocol 的 sendmsg 還會在不同地方 implement。依照前面的經驗,我們去搜尋 sk_prot 被實做的地方就能發現實際上會呼叫哪個 function。
1 2 3 4 5 6 7 8 9 |
struct sock { /* * Now struct inet_timewait_sock also uses sock_common, so please just * don’t add nothing before this first member (__sk_common) –acme */ struct sock_common __sk_common; .... #define sk_prot __sk_common.skc_prot ... |
1 2 3 4 5 6 |
struct sock_common { /* skc_daddr and skc_rcv_saddr must be grouped on a 8 bytes aligned * address on 64bit arches : cf INET_MATCH() */ ... struct proto *skc_prot; |
在 net 當中搜尋 struct proto: (grep -r . -e ‘struct proto’ | grep -v ‘ops’)
1 2 3 |
./ipv4/udp.c:struct proto udp_prot = { ./ipv4/raw.c:struct proto raw_prot = { ./ipv4/tcp_ipv4.c:struct proto tcp_prot = { |
看一下 tcp_prot
,實際實做的 function 就是 tcp_sendmsg:
1 2 3 4 5 6 7 |
struct proto tcp_prot = { ... .recvmsg = tcp_recvmsg, .sendmsg = tcp_sendmsg, ... }; EXPORT_SYMBOL(tcp_prot); |
tcp_sendmsg 在 net/ipv4/tcp.c 裡面:
1 2 3 4 5 6 7 8 9 10 11 |
int tcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t size) { int ret; lock_sock(sk); ret = tcp_sendmsg_locked(sk, msg, size); release_sock(sk); return ret; } EXPORT_SYMBOL(tcp_sendmsg); |
以下是完整的流程圖,由 ksys_write 一路到送入 wireless driver 前的 drv_wake_tx_queue。
cflow2dot -s ksys_write -t dipv4/ip_output.c include/net/neighbour.h net/ipv4/route.c include/net/route.h include/linux/netdevice.h net/mac80211/iface.c include/net/dst.h net/ipv4/tcp.c net/ipv4/tcp_output.c include/net/ip.h net/socket.c fs/read_write.c include/linux/fs.h net/mac80211/tx.c net/ipv4/af_inet.c net/socket.c -R –rankdir TB && eog cflow0.svgrv_wake_tx_queue __netdev_start_xmit rt_dst_alloc __mkroute_output -I”–include=_s –main=ksys_write” -i net/core/dev.c net/
cflow not following reference [src/parser.c:reference(name,line)]
Leave a Reply