net: a blocked socket sleeps until something happens, rather than every 10 ms
The AF_INET blocking paths -- connect, accept, read and write -- woke on every timer tick and looked to see whether anything had changed. That is a wakeup per blocked socket per 10 ms whether or not the network did anything, and it answers late even when the data was already sitting in the buffer. What stood in the way was a lock mismatch, and it is written down in the file now because it is the reason this is not simply sleep on the socket: sleep() wants a spinlock, and the lwIP lock is a sleeplock -- it must be, since a call into lwIP can reach the driver and the driver can sleep. Nor can a waiter hold the lwIP lock while sleeping, or the network thread could never deliver the thing it is waiting for. So the wait is on a counter with a spinlock of its own. A waiter reads the counter while it still holds the lwIP lock, right after checking its condition, then releases and sleeps only if the counter has not moved. The lwIP callbacks bump it and wake the channel. No wakeup can be lost: anything that changes a condition does so after the reader's snapshot, and therefore bumps the counter after it too, and the sleeper re-checks under the spinlock before committing. One counter and one channel for all sixteen sockets rather than one each. Waking a socket with nothing to do costs a few instructions, and a per-socket channel would have to be torn down in close() against callbacks that can still arrive -- which is a use-after-free waiting to be written. tcp_sent is registered now, on both connected and accepted pcbs. It was not needed while writes woke on the clock; with this change a write blocked on a full send buffer would otherwise sleep with nobody left to wake it, because an acknowledgement is the only thing that frees space. Verified: bootcheck -Net passes, 38 [ok], no failures. It exercises the path that matters -- nettest blocks in read() until the host answers, and the host answers only after reading what the guest sent, so the wakeup comes from a callback and not from a tick that would have fired anyway. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
f46fb41966
commit
3ad8114208
3 changed files with 120 additions and 31 deletions
|
|
@ -252,6 +252,12 @@ netinit(void)
|
|||
{
|
||||
if (net_started)
|
||||
return;
|
||||
// Before the check below, not after: the lock it initialises has to be
|
||||
// valid on a machine with no NIC too. Nothing can reach a wait there --
|
||||
// inet_new refuses while the stack is down -- but a lock whose validity
|
||||
// depends on that reasoning holding forever is not worth the two lines
|
||||
// saved.
|
||||
inet_init();
|
||||
if (!e1000_present()) {
|
||||
// Silent: a machine without an e1000 is not misconfigured, and this
|
||||
// runs on every boot.
|
||||
|
|
|
|||
Loading…
Reference in a new issue