By definition /dev/null
sinks anything written to it, so it doesn't matter if you write in append mode or not, it's all discarded. Since it doesn't store the data, there's nothing to append to, really.
So in the end, it's just shorter to write > /dev/null
with one >
sign.
As for the edited addition:
The open(2) manpage says lseek is called before each write to a file in append mode.
If you read closely, you'll see it says (emphasis mine):
the file offset is positioned at the end of the file, as if with lseek(2)
Meaning, it doesn't (need to) actually call the lseek
system call, and the effect is not strictly the same either: calling lseek(fd, SEEK_END, 0); write(fd, buf, size);
without O_APPEND
isn't the same as a write in append mode, since with separate calls another process could write to the file in between the system calls, trashing the appended data. In append mode, this doesn't happen (except over NFS, which doesn't support real append mode).
The text in the standard doesn't mention lseek
at that point, only that writes shall go the end of the file.
So, is truncating /dev/null actually unspecified?
Judging by the scripture you refer to, apparently it's implementation-defined. Meaning that any sane implementation will do the same as with pipes and TTY's, namely, nothing. An insane implementation might do something else, and perhaps truncation might mean something sensible in the case of some other device file.
And do the lseek calls have any impact on write performance?
Test it. It's the only way to know for sure on a given system. Or read the source to see where the append mode changes the behaviour, if anywhere.
Let's quickly review device files: In Linux, application programs communicate rad and write operations to the kernel through file descriptors. That works great for files, and it turned out that the same API could be used for character devices that produce and consume streams of characters, and block devices that read and write blocks of fixed size at a random access address, just by pretending that these are also files.
But a way was needed to configure those devices (set baud rates etc.), and for that, the ioctl call was invented. It just passes a data structure that's specific to the device and the kind of I/O control used to the kernel, and gets back the results in the same data structure, so it's a very generic extensible API and can be used for lots of things.
Now, how do network operations fit in? A typical network server application wants to bind to some network address, listen on a certain port (e.g. 80 for HTTP, or 22 for ssh), and if a client connects, it wants to send data to and receive data from this client. And the dual operations for the client.
It's not obvious how to fit this in with file operations (though it can be done, see Plan 9), that's why the UNIX designers invented a new API: sockets. You can find details in the section 2 man pages for socket
, bind
, listen
, connect
, send
and recv
. Note that while it is distinct from the file I/O API, the socket
call nevertheless also returns a file descriptor. There are numerous tutorials on how to use sockets on the web, google a bit.
So far this is all pure UNIX, nobody was talking about network interfaces at the time sockets were invented. And because this API is really old, it is defined for a variety of network protocols beyond the Internet protocol (look at the AF_*
constants), though only a few of those are supported in Linux.
But as computers started to get multiple network cards, some abstraction for this was needed. In Linux, that is the network interface (NI). It's not only used for a piece of hardware, but also for various tunnels, user application endpoints that server as tunnels like OpenVPN etc. As explained, the socket API isn't based on (special) files and independent of the filesystem. In the same way, network interfaces don't show up in the file system, either. However, the NIs are made available in the /proc
and /sys
filesystem (as well as other networking tunables).
A NI is simple a kernel abstraction of an endpoint where network packets enter and leave the kernel. Sockets, on the other hand, are used to communicate packets with applications. No socket needs to be involved with the processing of a packet. For example, when forwarding is enabled, a packet may enter on one NI and leave on another.
In that sense, sockets and network interfaces are totally independent.
But there had to be a way to configure NIs, just like you needed a way to configure block and character devices. And since sockets already returned a file descriptor, it was somewhat logical to just allow an ioctl
on that file descriptor. That's the netdevice interface you linked.
There are quite a few other abuses of system calls in a similar way, for example for packet filtering, packet capture etc.
All of this has grown piece after piece, and is not particularly logical in many places. If it had be designed all at once, one could probably have made a more orthogonal API.
Best Answer
So there are basically two different types of thing here:
/proc
and/sys
are examples here, as are FUSE custom filesystems likesshfs
orifuse
. There's much more diversity in these, because really they just refer to a filesystem with semantics that are in some sense 'custom'. Thus, when you read from a file under/proc
, you aren't actually accessing a specific piece of data that's been stored by something else writing it earlier, as under a normal filesystem. You're essentially doing a kernel call, requesting some information that's generated on-the-fly. And this code can do anything it likes, since it's just some function somewhere implementingread
semantics. Thus, you have the weird behavior of files under/proc
, like for instance pretending to be symlinks when they aren't really.The key is that
/dev
is actually, usually, one of the first kind. It's normal in modern distributions to have/dev
be something like a tmpfs, but in older systems, it was normal to have it be a plain directory on disk, without any special attributes. The key is that the files under/dev
are device nodes, a type of special file similar to FIFOs or Unix sockets; a device node has a major and minor number, and reading or writing them is doing a call to a kernel driver, much like reading or writing a FIFO is calling the kernel to buffer your output in a pipe. This driver can do whatever it wants, but it usually touches hardware somehow, e.g. to access a hard disk or play sound in the speakers.To answer the original questions:
There are two questions relevant to whether the 'file exists' or not; these are whether the device node file literally exists, and whether the kernel code backing it is meaningful. The former is resolved just like anything on a normal filesystem. Modern systems use
udev
or something like it to watch for hardware events and automatically create and destroy the device nodes under/dev
accordingly. But older systems, or light custom builds, can just have all their device nodes literally on the disk, created ahead of time. Meanwhile, when you read these files, you're doing a call to kernel code which is determined by the major and minor device numbers; if these aren't reasonable (for instance, you're trying to read a block device that doesn't exist), you'll just get some kind of I/O error.The way it works out what kernel code to call for which device file varies. For virtual filesystems like
/proc
, they implement their ownread
andwrite
functions; the kernel just calls that code depending on which mount point it's in, and the filesystem implementation takes care of the rest. For device files, it's dispatched based on the major and minor device numbers.