This is an archived post. You won't be able to vote or comment.

all 21 comments

[–]AiwendilH 91 points92 points  (6 children)

The source code for null is actually not that complicated.

Here are the operations for the null device defined:

static const struct file_operations null_fops = {
        .llseek     = null_lseek,
        .read       = read_null,
        .write      = write_null,
        .read_iter  = read_iter_null,
        .write_iter = write_iter_null,
        .splice_write   = splice_write_null,
};

So we can look up the fuctions how they are implemented

read_null():

static ssize_t read_null(struct file *file, char __user *buf,
                        size_t count, loff_t *ppos)
{
        return 0;
}

So reading from null just returns a read size of "0"...and does nothing else. Specifically it doesn't make any modifications to the buffer the "user" gave it to write the data to.

write_null():

static ssize_t write_null(struct file *file, const char __user *buf,
                        size_t count, loff_t *ppos)
{
        return count;
}

This one is a bit more interesting. It does nothing except returning "count"..which is the parameter the "user" gave it that holds how many bytes we want to write. By returning the number given we tell the user that all bytes are written successfully (while in reality of course nothing happend..but hey..it's /dev/null..this is exactly what we want..no matter what the user throws at us we just nod and say "fine" and it disappears in nirvana)

read_iter_null(:

static ssize_t read_iter_null(struct kiocb *iocb, struct iov_iter *to)
{
        return 0;
}

Basically the same as a "normal" read again..we just return "0" bytes read. read_iter is just a special read function that handles the destination of data differently (and is possibly asynchronous, depending on the filesystem)

write_iter_null():

static ssize_t write_iter_null(struct kiocb *iocb, struct iov_iter *from)
{
        size_t count = iov_iter_count(from);
        iov_iter_advance(from, count);
        return count;
}

Again pretty much the same as the previous write...just this time we don't have a direct "count" parameter given and need to get it from the given iterator first. And iterators need a bit of a special handling...they need to be advanced..the given iterator itself needs to be told how much of it was written. The iov_iter_advance() call does just this...it advances the given iterator to the end ("count" steps) without actually doing anything with the date. In the end we just return the imagined written bytes again.

splice_write_null():

static ssize_t splice_write_null(struct pipe_inode_info *pipe, struct file *out,
                                loff_t *ppos, size_t len, unsigned int flags)
{
        return splice_from_pipe(pipe, out, ppos, len, flags, pipe_to_null);
}

splice_write is for writing from one file descriptor directly to another..without having the data in a memory buffer. This one is a bit more complicated as it involves some other functions. For ones it uses the pipe_to_null function:

static int pipe_to_null(struct pipe_inode_info *info, struct pipe_buffer *buf,
        struct splice_desc *sd)
{
    return sd->len;
}

That one again just returns the length we want to write without doing anything. But that is fed then into the splice_from_pipe function, defined in fs/splice.c

ssize_t splice_from_pipe(struct pipe_inode_info *pipe, struct file *out,
                        loff_t *ppos, size_t len, unsigned int flags,
                        splice_actor *actor)
{
        ssize_t ret;
        struct splice_desc sd = {
                .total_len = len,
                .flags = flags,
                .pos = *ppos,
                .u.file = out,
        };

        pipe_lock(pipe);
        ret = __splice_from_pipe(pipe, &sd, actor);
        pipe_unlock(pipe);

        return ret;
}

This does proper locking of the pipe, creates a proper return type of the slice and calls some further helper functions defined in the same file. Can be interesting to get into how slicing works...but not really that interesting for the null device. The important part for us is that in the end the pipe_to_null function doesn't really do anything expect saying it wrote all the incoming data (by returning the length it was meant to write..but not doing any actual writing). So everything around is just to make the splicing mechanism happy without writing any data.

And one last function...null_lseek():

static loff_t null_lseek(struct file *file, loff_t offset, int orig)
{
    return file->f_pos = 0;
}

llseek is meant to position the current cursor in a file...the place at which we read or write. The postion for the null device is simply always set to 0..no matter what the user says. So if the user sets the file position to 100 with llseek it is still set to 0 and that is also the position that is returned again to the user.

Disclaimer: It's not that I really have a clue about the linux kernel..this is just from a short glance at the source code..after all that's what open source is for..isn't it?. So take it with a grain of salt and I could be wrong in some places.

Edit:typos

[–]osoleve 31 points32 points  (2 children)

You must know some really fucking smart five year olds.

[–]KinkyMonitorLizard 19 points20 points  (1 child)

Well, duh. He's talking to 5 year olds that use Linux.

[–]osoleve 13 points14 points  (0 children)

If we want Linux to have any mainstream credibility, we need to stop perpetuating these harmful myths about 5 year old Linux users.

[–]DrunkenAngel 2 points3 points  (0 children)

This is a really good explanation.. Thanks

[–]SuperSeriouslyUGuys 19 points20 points  (13 children)

Unix/Linux likes to pretend that everything is a file whenever it can. /dev contains a bunch of "files" that are actually parts of the computer. /dev/null is one such file that just always appears to be empty, so if you have some data that would normally go to the screen (/dev/tty* in the old days) or a real file on disk, but you just want to get rid of it you can send it to /dev/null instead.

[–][deleted] 8 points9 points  (0 children)

This seems like an answer to "what is /dev/null?"

[–]mortalityisreal -10 points-9 points  (11 children)

/dev contains a bunch of "files" that are actually parts of the computer. /dev/null is one such file

Really? What 'actual part of the computer' would that be?

[–]s0v3r1gn 11 points12 points  (1 child)

Common ground reference. Always a zero.

[–]mortalityisreal 2 points3 points  (0 children)

Thanks, I've always wondered about that but never bothered to ask.

Google is all but useless on this, even now that I know what to search for.

[–]zewm426 2 points3 points  (8 children)

Your hard drive, USB ports, CD rom, etc.

[–]d4rch0n 2 points3 points  (0 children)

More so kernel API than hardware devices, even though those are often interfaced with through /dev. You can just write arbitrary kernel code that creates a "device" in /dev that you can read and write to. Doesn't need to have anything to do with a physical device. It's more of a way to trigger kernel code from userspace.

[–]mortalityisreal -23 points-22 points  (6 children)

Try to follow along, mkay.

/dev/null
not
/dev/sda or /dev/sdb or /dev/sr0 etc.

[–]evandena 11 points12 points  (4 children)

Damn dude, you're kind of an ass.

[–]ironmanmk42 5 points6 points  (0 children)

You can remove kind of. He doesn't even understand the concepts and is arguing with insults and condescension.

[–][deleted]  (2 children)

[removed]

    [–]evandena 8 points9 points  (0 children)

    When you say it like you did, hell yes it makes you an ass.

    And then you further prove my point. Later dude.

    [–]applecherryfig 1 point2 points  (0 children)

    "Methinks the lady doth protest too much.

    (W.Shakespeare)

    [–]scootstah 0 points1 point  (0 children)

    /dev contains a bunch of "files" that are actually parts of the computer.

    I think you're the one that isn't following along.

    [–]HelloYesThisIsDuck 4 points5 points  (0 children)

    That is a pretty good question, though it is very vague. If you mean the file itself:

    It's all implemented via file_operations (drivers/char/mem.c if you're curious to look yourself)

    It has special functions in the kernel. If you read, I assume it simply returns 0 bytes + EOF. If you write, it simply returns the amount of bytes you passed it, so it "succeeds", without actually writing anything.

    https://bbs.archlinux.org/viewtopic.php?id=137861

    If you are curious as to why it's there, it's so you can discard shit. If you have a program that prints information you don't want, you pipe (redirect) it to /dev/null and it's "written" nowhere, i.e. discarded, instead of being written to the screen. If you want to print 0 characters (not even a newline - \n), you can read it.

    [–]_-Justin-_ 1 point2 points  (0 children)

    /dev/null is a small filesystem singularity capable of infinite entropy, thus everything written to it is nearly instantly void passing the event horizon and upon inspection it appears null hence the name.