1

I was trying to figure out how file operations in drivers work. I know there are several file operations but the functions for these operations are called with several arguments while the operations themselves are defined without any.

So if I have this -

static const struct file_operations proc_myled_operations = { 
     .open = proc_myled_open, 
     .read = seq_read, 
     .write = proc_myled_write, 
     .llseek = seq_lseek, 
     .release = single_release 
 };

Now I know that kernel level drivers can only be accessed as files from the user application. This is an embedded system so I have some LEDs that I can turn on by writing to their memory mapped registers.

So the .write or "proc_myled_write" call will execute when I turn an led on which I can do by opening this file using fopen and then writing to it by using fputs. But if .write is mapped as "proc_myled_write and this function has arguments like so -

static ssize_t proc_myled_write(struct file *file, const char __user * buf, 
size_t count, loff_t * ppos)

What happens to the arguments? There is no function call for the above function with those arguments. I've seen this in several drivers. I just used this one because it was a simple example. How are the file operations mapped to these functions? How does the, for example, "write" in user space trace to the write in the driver?

Thank you.

maverick1989
  • 342
  • 7
  • 20

2 Answers2

5

I'm not exactly sure what you mean when you say "There is no function call for the above function with those arguments."

The prototype for these functions is defined in the declaration for struct file_operations itself.

Here is the first few lines from the struct declaration:

struct file_operations {
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_t, int);
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    ...

While the arguments are not named in the declaration, you can clearly see that the write() function is declared with 4 parameters matching the types that you mention in your question.

When you assign the function to its appropriate field (proc_myled_operations.write = proc_myled_write) you are simply passing a pointer to the write function declared and defined in your module. Pointers to functions themselves do not need parameters.


Ok, so you're question really is: "How does the user space system call eventually call the write function in your module?" Good question! I recommend editing your question to make that clearer for future readers.

Well, let's see if I can follow the paper trail. I discovered this document to give me the starting location to look in the code for the write() system call. It's very very old, but hey, not everything changes in the kernel! We start our journey at the write() system call declaration in fs/read_write.c:

SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
            size_t, count)

It uses the file descriptor fd to get the struct file created when you registered your character driver. Then it gets the current position in the file and calls vfs_write().

ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)

And it is in this function see the following line:

ret = file->f_op->write(file, buf, count, pos);

There it is!

To allay any doubts as to the type of file->f_op, we take a look at the definition of struct file and see the following definition for the f_op field:

    const struct file_operations    *f_op;

So it must be the struct file_operations you passed in when you registered your driver. Phew!

Hopefully all of these links will show you how to follow the trail for other system calls if you are curious.

Benjamin Leinweber
  • 2,774
  • 1
  • 24
  • 41
  • Yes exactly! That's how I thought I had understood it. But then something needs to call the write function somewhere with arguments right? I know the file structure is unique to the kernel space drivers and user case applications don't see it/can't use it but what about the other three arguments? How does the write function (or the proc_myled_write, either ways) know what memory the arguments are stored in? – maverick1989 Jul 16 '13 at 01:22
  • Thanks Ben!! Can I call you Ben? That was a really good explanation! I also took your suggestion and edited my question. Thanks again... – maverick1989 Jul 16 '13 at 12:02
  • But before a userland program can perform a `write()` syscall, it has to perform an `open()` on a file (or device) to obtain a `file descriptor`. A brief description of that is [here](http://stackoverflow.com/questions/14501437/how-does-open-works-for-normal-file-and-device-drivers/14513460#14513460) – sawdust Jul 16 '13 at 22:15
1

@Maverick, write prototype or signature in kernel is ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *). From user space application you will be issuing open, write system call to turn on/off the LED. Write system call signature in user space is int write(int fd, const char *buf, size_t count). So when you call write from user space, fd (file descriptor) passed reaches the virtual file system(vfs), maintains linked list of the open file descriptor table(OFDT), thus according to the fd, OFDT has an pointer filp (file pointer) which point to the file opened for ex: device node "/dev/xxx" or any other file for that matter. The rest buf and count are same arguments passed from the user space to the kernel space. Last loff_t *fpos comes into picture if you want to seek the file for ex: using lseek or fseek on the file opened, if seek then the file pointer(loff_t fpos) position gets changed accordingly. Hope I cleared your doubt :-)

Gautham Kantharaju
  • 1,735
  • 1
  • 21
  • 24
  • Most of my doubts, yes. However, one statement in your answer - "Write system call signature in user space is int write(int fd, const char *buf, size_t count)". This call doesn't exist in the sample file I am looking at. My example code has the driver and the user space call to open the driver file and write to it. However, it does this by using fopen to open the file under /dev/myled (myled is the name of the driver object) for writing (in "w" mode) and then does a puts to write to it. That is all. There isn't an actual function CALL like you have said. That is the source of my confusion. – maverick1989 Jul 16 '13 at 02:55
  • @Maverick, fopen turn's to open system call for your info, likewise fread/fwrite turns to read/write system call before reaching vfs layer or kernel space. – Gautham Kantharaju Jul 16 '13 at 03:07
  • Okay but regardless, somewhere, something needs to call that file operations write function with those arguments right? – maverick1989 Jul 16 '13 at 03:27
  • @Maverick, VFS layer, OFDT are hidden process from user space application perspective, will never come to know about what happens in VFS layer. Just for understanding purpose I have explained. User space application will issue open/fopen it reaches the kernel space and opens up the appropriate file and if successful then can read/write to that particular file. Use strace command then you will come to know what are all the system call your example program uses. – Gautham Kantharaju Jul 16 '13 at 03:35
  • Calling of write function with those arguments is taken care by VFS layer. For ex:- foo call from user space is replaced by VFS layer as filp->f_op->foo(with appropriate arguments). So read call from user space will become filp->f_op->read(with appropriate arguments) likewise for all the system call. – Gautham Kantharaju Jul 16 '13 at 03:42
  • Your explanation was good but I think the above was more clearer. I wish I could set both as correct answers but unfortunately SO only allows me to set one as correct. Thank you very much for your answer though. – maverick1989 Jul 16 '13 at 12:03