Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

net: tcp: tsq: Convert from tasklet to BH workqueue

The only generic interface to execute asynchronously in the BH context is
tasklet; however, it's marked deprecated and has some design flaws. To
replace tasklets, BH workqueue support was recently added. A BH workqueue
behaves similarly to regular workqueues except that the queued work items
are executed in the BH context.

This patch converts TCP Small Queues implementation from tasklet to BH
workqueue.

Semantically, this is an equivalent conversion and there shouldn't be any
user-visible behavior changes. While workqueue's queueing and execution
paths are a bit heavier than tasklet's, unless the work item is being queued
every packet, the difference hopefully shouldn't matter.

My experience with the networking stack is very limited and this patch
definitely needs attention from someone who actually understands networking.

Signed-off-by: Tejun Heo <tj@kernel.org>
Reviewed-by: Jason Xing <kerneljasonxing@gmail.com>
Reviewed-by: Eric Dumazet <edumazet@google.com>
Cc: David Ahern <dsahern@kernel.org>
Link: https://patch.msgid.link/aFBeJ38AS1ZF3Dq5@slm.duckdns.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

authored by

Tejun Heo and committed by
Jakub Kicinski
fd0406e5 e15962ae

+20 -20
+1 -1
include/net/tcp.h
··· 321 321 #define TCP_DEC_STATS(net, field) SNMP_DEC_STATS((net)->mib.tcp_statistics, field) 322 322 #define TCP_ADD_STATS(net, field, val) SNMP_ADD_STATS((net)->mib.tcp_statistics, field, val) 323 323 324 - void tcp_tasklet_init(void); 324 + void tcp_tsq_work_init(void); 325 325 326 326 int tcp_v4_err(struct sk_buff *skb, u32); 327 327
+1 -1
net/ipv4/tcp.c
··· 5242 5242 tcp_v4_init(); 5243 5243 tcp_metrics_init(); 5244 5244 BUG_ON(tcp_register_congestion_control(&tcp_reno) != 0); 5245 - tcp_tasklet_init(); 5245 + tcp_tsq_work_init(); 5246 5246 mptcp_init(); 5247 5247 }
+18 -18
net/ipv4/tcp_output.c
··· 1066 1066 * needs to be reallocated in a driver. 1067 1067 * The invariant being skb->truesize subtracted from sk->sk_wmem_alloc 1068 1068 * 1069 - * Since transmit from skb destructor is forbidden, we use a tasklet 1069 + * Since transmit from skb destructor is forbidden, we use a BH work item 1070 1070 * to process all sockets that eventually need to send more skbs. 1071 - * We use one tasklet per cpu, with its own queue of sockets. 1071 + * We use one work item per cpu, with its own queue of sockets. 1072 1072 */ 1073 - struct tsq_tasklet { 1074 - struct tasklet_struct tasklet; 1073 + struct tsq_work { 1074 + struct work_struct work; 1075 1075 struct list_head head; /* queue of tcp sockets */ 1076 1076 }; 1077 - static DEFINE_PER_CPU(struct tsq_tasklet, tsq_tasklet); 1077 + static DEFINE_PER_CPU(struct tsq_work, tsq_work); 1078 1078 1079 1079 static void tcp_tsq_write(struct sock *sk) 1080 1080 { ··· 1104 1104 bh_unlock_sock(sk); 1105 1105 } 1106 1106 /* 1107 - * One tasklet per cpu tries to send more skbs. 1108 - * We run in tasklet context but need to disable irqs when 1107 + * One work item per cpu tries to send more skbs. 1108 + * We run in BH context but need to disable irqs when 1109 1109 * transferring tsq->head because tcp_wfree() might 1110 1110 * interrupt us (non NAPI drivers) 1111 1111 */ 1112 - static void tcp_tasklet_func(struct tasklet_struct *t) 1112 + static void tcp_tsq_workfn(struct work_struct *work) 1113 1113 { 1114 - struct tsq_tasklet *tsq = from_tasklet(tsq, t, tasklet); 1114 + struct tsq_work *tsq = container_of(work, struct tsq_work, work); 1115 1115 LIST_HEAD(list); 1116 1116 unsigned long flags; 1117 1117 struct list_head *q, *n; ··· 1181 1181 } 1182 1182 EXPORT_IPV6_MOD(tcp_release_cb); 1183 1183 1184 - void __init tcp_tasklet_init(void) 1184 + void __init tcp_tsq_work_init(void) 1185 1185 { 1186 1186 int i; 1187 1187 1188 1188 for_each_possible_cpu(i) { 1189 - struct tsq_tasklet *tsq = &per_cpu(tsq_tasklet, i); 1189 + struct tsq_work *tsq = &per_cpu(tsq_work, i); 1190 1190 1191 1191 INIT_LIST_HEAD(&tsq->head); 1192 - tasklet_setup(&tsq->tasklet, tcp_tasklet_func); 1192 + INIT_WORK(&tsq->work, tcp_tsq_workfn); 1193 1193 } 1194 1194 } 1195 1195 ··· 1203 1203 struct sock *sk = skb->sk; 1204 1204 struct tcp_sock *tp = tcp_sk(sk); 1205 1205 unsigned long flags, nval, oval; 1206 - struct tsq_tasklet *tsq; 1206 + struct tsq_work *tsq; 1207 1207 bool empty; 1208 1208 1209 1209 /* Keep one reference on sk_wmem_alloc. 1210 - * Will be released by sk_free() from here or tcp_tasklet_func() 1210 + * Will be released by sk_free() from here or tcp_tsq_workfn() 1211 1211 */ 1212 1212 WARN_ON(refcount_sub_and_test(skb->truesize - 1, &sk->sk_wmem_alloc)); 1213 1213 ··· 1229 1229 nval = (oval & ~TSQF_THROTTLED) | TSQF_QUEUED; 1230 1230 } while (!try_cmpxchg(&sk->sk_tsq_flags, &oval, nval)); 1231 1231 1232 - /* queue this socket to tasklet queue */ 1232 + /* queue this socket to BH workqueue */ 1233 1233 local_irq_save(flags); 1234 - tsq = this_cpu_ptr(&tsq_tasklet); 1234 + tsq = this_cpu_ptr(&tsq_work); 1235 1235 empty = list_empty(&tsq->head); 1236 1236 list_add(&tp->tsq_node, &tsq->head); 1237 1237 if (empty) 1238 - tasklet_schedule(&tsq->tasklet); 1238 + queue_work(system_bh_wq, &tsq->work); 1239 1239 local_irq_restore(flags); 1240 1240 return; 1241 1241 out: ··· 2634 2634 if (refcount_read(&sk->sk_wmem_alloc) > limit) { 2635 2635 /* Always send skb if rtx queue is empty or has one skb. 2636 2636 * No need to wait for TX completion to call us back, 2637 - * after softirq/tasklet schedule. 2637 + * after softirq schedule. 2638 2638 * This helps when TX completions are delayed too much. 2639 2639 */ 2640 2640 if (tcp_rtx_queue_empty_or_single_skb(sk))