I have been experimenting with GNU Radio and came across the tunnel.py program. This program allows you to tunnel IP traffic over a wireless radio link using Linux TUN/TAP devices. For the most part it is working however one part of the code is confusing me.
There is a class which implements a 'basic MAC layer'. This class has a callback function which writes a new packet to the TUN device. This function (phy_rx_callback
) is called from a separate thread.
The function main_loop
does a carrier sense before transmitting a new packet. The thing I don't understand is why it is sensing a receive channel before transmitting on a separate non-overlapping transmit channel.
Both the RX and TX channels are separate frequencies, and our hardware allows full-duplex communication.
SO, my question is with main_loop
executing, what are the implications of another thread asynchronously calling the phy_rx_callback
function? The problem is I am trying to understand the purpose of the carrier sense loop, I found that commenting that code severely decreases performance. It doesn't make sense to me that you would monitor a receive channel before using a transmit channel, essentially turning it into half-duplex. Then I don't see the purpose of using two frequencies, one for transmit and one for receive. I began to wonder if there was a strange threading issue at work here.
A single instance of the cs_mac
class is created initially. A 'pointer' to the rx_callback function is passed down a few levels to the thread class which actually calls it. Here is the cs_mac class:
class cs_mac(object):
def __init__(self, tun_fd, verbose=False):
self.tun_fd = tun_fd # file descriptor for TUN/TAP interface
self.verbose = verbose
self.tb = None # top block (access to PHY)
def set_top_block(self, tb):
self.tb = tb
def phy_rx_callback(self, ok, payload):
if self.verbose:
print "Rx: ok = %r len(payload) = %4d" % (ok, len(payload))
if ok:
os.write(self.tun_fd, payload)
def main_loop(self):
min_delay = 0.001 # seconds
while 1:
payload = os.read(self.tun_fd, 10*1024)
if not payload:
self.tb.send_pkt(eof=True)
break
if self.verbose:
print "Tx: len(payload) = %4d" % (len(payload),)
delay = min_delay
while self.tb.carrier_sensed():
sys.stderr.write('B')
time.sleep(delay)
if delay < 0.050:
delay = delay * 2 # exponential back-off
self.tb.send_pkt(payload)
Ok, so using ctypes.CDLL('libc.so.6').syscall(186))
, which calls gettid
I discovered that the thread calling the rx_callback
function has the same PID, but a different TID.
The question becomes, what are the implications of having a separate thread call a function from an object in the main thread (while that thread is constantly looping)?
The function main_loop does a carrier sense before transmitting a new packet. The thing I don't understand is why it is sensing a receive channel before transmitting on a separate non-overlapping transmit channel.
The CSMA/CA is intended to be used with half-duplex systems, where all nodes use the same frequency to TX and RX. So you are right, there is no point in sensing the RX channel if you are transmitting in a different one.
carrier_sensed() is called in the receive_path.py file so it should run in the RX thread. In my code I comment out the lines sys.stderr.write('B') and time.sleep(delay) and this does not seem to affect performance. It might be different in my case since I use an XCVR daughter board which is half-duplex.