Blog

Let DCOP Tell You the Signature

A DCOP call failed today even though the method name was correct. The method name, unfortunately, was only part of the name that mattered. I had sent an int; the exported function expected an unsigned value.

Before changing code, I ask the running object what it exposes:

dcop myapp MainIface

The output includes complete function signatures. In this case it shows void setPage(unsigned int). I then call it from the command line with a small test argument:

dcop myapp MainIface setPage 3

DCOP identifies methods by their signatures and marshals known Qt types through QDataStream. The sending and receiving sides must therefore agree on argument types, order, and return type. A visually similar C++ declaration is not necessarily the same wire contract.

For custom values, both programs need compatible streaming operators and type registration. I avoid custom structures in small public interfaces when a QString, QCString, or simple list expresses the operation clearly. Fewer private types also make the command-line client useful.

When writing the C++ caller, I keep the request and reply types visible next to each other:

QByteArray data;
QDataStream args(data, IO_WriteOnly);
unsigned int page = 3;
args << page;

QCString replyType;
QByteArray replyData;
if (!client->call(app, "MainIface", "setPage(unsigned int)",
                  data, replyType, replyData)) {
    kdWarning() << "DCOP setPage call failed" << endl;
}

For a function with a return value, I verify replyType before extracting from replyData. Deserializing a reply under the wrong assumption merely converts a useful interface error into strange application state.

The application and object names should not be copied from a single run if multiple instances are possible. I list registered clients and decide whether the program is meant to address one instance, every instance, or the instance owning a particular document. A suffix added to keep names unique is normal, not a DCOP server losing its arithmetic.

I also preserve failures in scripts. The command-line client returns useful status, and a shell script should check it rather than carrying on after the target application has exited. Interprocess calls cross a lifetime boundary; yesterday’s object name is not a reservation for today.

I keep the exported interface narrow and test it independently of the window. If the command works but a toolbar action does not, DCOP has been acquitted and I can inspect the connection instead.

I let dcop provide the authoritative spelling instead of copying it from memory. Memory is fast, local, and apparently not type-safe.

Moving a KDE 3 Application to 3.1

After installing KDE 3.1, I rebuilt a small KDE 3.0 application expecting either complete success or a useful compiler failure. It built cleanly and then displayed a toolbar assembled from an older installed XML file. Compatibility had done its job; my installation habits had not.

The move from 3.0 to 3.1 is not the upheaval that the KDE 2 to KDE 3 port was. The first task should be a clean rebuild against the new headers and libraries, not a rewrite:

make -f Makefile.cvs
mkdir build-31
cd build-31
../configure --prefix="$KDEDIR" --enable-debug
make
make install

I check the configure summary to ensure it found the intended Qt and KDE prefixes. Having 3.0 under one prefix and 3.1 under another can produce a convincing but incoherent mixture of headers, libraries, services, and data files. kde-config --prefix and kde-config --path data help explain what the running session will search.

Installed resources matter as much as the executable. XMLGUI files, icons, desktop entries, service descriptions, and translations may survive from an earlier build. During testing I inspect the installed paths and remove only obsolete copies belonging to my application. Otherwise a changed action name in the source can appear to have no effect because the shell merged an old resource file.

I then exercise integration points: actions and standard shortcuts, session restoration, DCOP interfaces, KIO URLs, printing, and embedded parts. These boundaries are more likely to expose an assumption than an ordinary button click. Running from a terminal catches connection warnings and missing service messages that a polished window politely conceals.

If I choose to use a new 3.1 facility, I make that a separate change after the compatible build works. This keeps the required minimum version honest. Merely compiling on my workstation does not mean every user has the same minor release.

I also keep the old configuration file for one run and start with an empty one for another. Migration defects and default-setting defects often cancel each other on a developer’s account. Testing both states prevents an old saved value from making the fresh installation appear correct.

My preference is to preserve KDE 3.0 compatibility unless the new feature materially improves the program. The caveat is that testing both versions then becomes real work, including compile tests against both sets of development files. Compatibility declared but never built is just a hopeful comment with better publicity.

Module Parameters in Linux 2.4

I was rebuilding a test module merely to change its debug level. That is a fine way to turn a ten-second experiment into a small ritual. Linux 2.4 module parameters are a better fit.

For a simple integer, the old module parameter declaration is compact:

static int debug;
MODULE_PARM(debug, "i");
MODULE_PARM_DESC(debug, "enable diagnostic messages");

I can then set it while loading:

insmod sample.o debug=1

The type string tells the module loader how to interpret the value. Other forms support strings and arrays, but I avoid elaborate configuration here. Parameters are useful for hardware choices and diagnostics needed at initialization, not as a private replacement for a proper user interface.

Validation still belongs in the initialization function. A parameter arriving from insmod is input, not a promise. If a buffer count must be positive and bounded, the module should reject nonsense with -EINVAL before allocating resources.

I also print the effective non-default setting at load time when it materially changes behavior. That makes dmesg useful when somebody reports that the driver behaves differently on one machine.

My preference is a quiet default and one obvious parameter for debugging. Five boolean switches rapidly become a combination lock whose correct setting nobody remembers. Recompiling was tedious, but at least it did not accept debug=banana with a straight face.

Reading an skb Without Regretting It

I added a packet check to a Linux 2.4 network path and initially treated skb->data as if it always began with the header I wanted. It did in my first test. Networking code has a generous way of rewarding that assumption just long enough to become embarrassing.

The sk_buff carries packet data plus bookkeeping used as the packet moves through layers. Its head, data, tail, and end pointers describe the allocated area and the currently occupied bytes. Protocol processing can pull a consumed header from the front or push space for a new one without copying the whole packet.

Before reading a header, I check that the required bytes are present and use the header position appropriate to that point in the stack. In a simple driver receive path, the driver allocates an skb, reserves alignment if needed, copies or maps packet bytes, sets the protocol, and hands it upward:

skb = dev_alloc_skb(length + 2);
if (!skb)
    return -ENOMEM;

skb_reserve(skb, 2);
memcpy(skb_put(skb, length), packet, length);
skb->dev = dev;
skb->protocol = eth_type_trans(skb, dev);
netif_rx(skb);

Calling skb_put() extends the valid data at the tail and returns the start of the added area. Writing beyond that area corrupts memory, so the allocation and length must agree. eth_type_trans() interprets the Ethernet header, sets the protocol, and adjusts the skb for the network layer.

Ownership changes at netif_rx(). After handing the skb to the network stack, the driver must not free or inspect it as though it still belongs to the receive routine. Error paths before that handoff must free what they allocated.

Transmit has the opposite pressure. The driver’s hard_start_xmit receives an skb and must obey the device queue rules. If hardware resources are exhausted, stopping the queue and waking it when descriptors become available is better than quietly dropping ownership conventions. Interrupt and bottom-half paths also require locking suited to their contexts; sleeping locks are not available there.

I prefer using skb helpers over pointer arithmetic, even when the arithmetic looks obvious. The helpers document whether code is adding, removing, or reserving bytes and retain their checks in debugging builds. The caveat is that not every skb is necessarily laid out as one convenient linear region at every layer, so code must respect the guarantees of its exact call site. Packets are simple on diagrams. The diagrams do not have cache lines.

Building a Module for the Running Kernel

I compiled a small module against the headers in /usr/include/linux, loaded it, and received a cheerful list of unresolved symbols. The mistake was treating user-space headers as a kernel build environment.

A module must be built for the kernel that will load it. I first identify that kernel and locate its configured source tree:

uname -r
ls -l /lib/modules/`uname -r`/build

On a packaged system the build link commonly points to the matching kernel sources or prepared headers. The tree must have the configuration and generated headers corresponding to the installed kernel. Merely unpacking a similar 2.4 source archive is not enough.

For a tiny experiment, the compile command shows the important ingredients:

gcc -D__KERNEL__ -DMODULE -O2 -Wall \
    -I/lib/modules/`uname -r`/build/include \
    -c sample.c -o sample.o

Real module makefiles should take their flags from the target kernel’s build setup rather than accumulating copied guesses. Processor options, SMP support, and symbol versioning can all affect the expected object. If the kernel was built with module versions, the build also needs the matching version definitions; disabling the complaint does not make incompatible structures compatible.

I compare the module and running kernel before loading, then inspect the actual diagnostic:

insmod sample.o
dmesg

An unresolved symbol may mean the providing facility is not configured, is itself a module not yet loaded, was not exported, or has a different version. depmod -a and modprobe handle dependency information for installed modules, while insmod is useful when I deliberately want the direct error from one test object.

The important mechanism is that a loadable module links against symbols exported by the running kernel and other loaded modules. It is not a normal program with its own copy of kernel interfaces. Configuration differences therefore matter even when function names look identical.

Optimization is not optional decoration here. Kernel headers use inline functions and assumptions expected by the normal build flags, so an unusual hand-written command can expose failures absent from an ordinary kernel build. I use the short command only to understand the pieces, then put repeatable work in a makefile tied to the configured tree.

Before installing, I test removal as well as insertion and confirm that no old object with the same module name is already loaded. Debugging yesterday’s module while reading today’s source is a surprisingly convincing experience.

I prefer building against the exact configured tree and letting version checks fail loudly. The caveat is that matching uname -r is necessary but not magical; a locally rebuilt kernel with the same release string can still differ. Names are labels, not fingerprints, despite what some build directories would like me to believe.