A network card device driver is usually started either when the kernel inserts a packet in its transmit queue (as described in the previous section), or when a packet is received from the communication channel. Let’s focus here on packet transmission.
As we have seen, the qdisc_run( )
function is
invoked whenever the kernel wishes to activate a network card device
driver; it is also executed by the NET_TX_SOFTIRQ
softirq, which is implemented by the net_tx_action( )
function (see Section 4.7).
Essentially, the qdisc_run( )
function checks
whether the network card device is idle and can thus transmit the
packets in the queue. If the device cannot do this — for
instance, because the card is already busy in transmitting or
receiving a packet, the queue has been stopped to avoid flooding the
communication channel, or for whatever other reason — the
NET_TX_SOFTIRQ
softirq is activated and the
current execution of qdisc_run( )
is terminated.
At a later time, when the scheduler selects a
ksoftirqd_CPUn kernel thread, the
net_tx_action( )
function invokes
qdisc_run( )
again to retry the packet
transmission.
In particular, qdisc_run( )
performs the following
actions:
Checks whether the packet queue is
“stopped” — that is, whether
a suitable bit in the state
field of the
net_device
network card object is set. If it is
stopped, the function returns immediately.
Invokes the qdisc_restart( )
function, which in
turn performs the following actions:
Invokes the dequeue
method of the
Qdisc
packet queue to extract a packet from the
queue. If the queue is empty, it terminates.
Checks whether a packet sniffing policy is enforced on the kernel,
telling it to pass a copy of each outgoing packet to a local socket;
in this case, the function invokes the dev_queue_xmit_nit( )
function to do the job. We won’t discuss
this further.
Invokes the hard_start_xmit
method of the
net_device
object that describes the network card
device.
If the hard_start_xmit
method fails in
transmitting the packet, it reinserts the packet in the queue and
invokes cpu_raise_softirq( )
to schedule the
activation of the NET_TX_SOFTIRQ
softirq.
If the queue is now empty, or the hard_start_xmit
method fails in transmitting the packet, the function terminates.
Otherwise, it jumps to Step 1 to process another packet in the queue.
The hard_start_xmit
method is specific to the
network card device and takes care of transferring the packet from
the socket buffer to the device’s memory. Typically,
the method limits itself to activate a DMA transfer. In PCI-based
network cards, moreover, a small number of DMA transfers may usually
be booked in advance: they are automatically activated by the card
whenever it finishes the ongoing DMA transfers. If the card is not
able to accept further packets because the device’s
memory is full, the method stops the packet queue by setting the
proper bit in the state
field of the
net_device
object. Therefore, the
qdisc_run( )
function terminates and is presumably
executed again later by the softirq.
When a DMA transfer ends, the card raises an interrupt. The corresponding interrupt handler, in turn, performs the following actions:
Acknowledges the interrupt issued by the card.
Checks for transmission errors, updates driver statistics, and so on.
Invokes, if necessary, the cpu_raise_softirq( )
function to schedule the activation of the softirq.
If the queue is stopped, resets the bit in the
state
field of the net_device
object and restarts packet processing.
As you see, network card device drivers work like disk device drivers: the real work is mostly done in interrupt handlers and deferrable functions, so that usual processes are not blocked waiting for packet transmissions.