Understanding loff_t *offp for file_operations

33,337

Solution 1

"loff_t" is a "long offset", i.e., a seek position that unifies the crazy diversity of off_t, off64_t, and so on, so that drivers can just use loff_t and not worry about it.

The pointer itself, at the time you get into the driver, points to the offset provided by the user (assuming it's user code doing the driver access—technically the kernel can provide its own, but the user case is the one to think about) via lseek or llseek or lseek64, etc., and then by ordinary read and write operations. Consider the case of a regular on-disk file: when you first open the file, you (as a user) get the kernel to provide a data structure that keeps track of your current position in the file, so that if you read or write some bytes, the next read or write picks up from where you left off.

Furthermore, if you dup the file descriptor, or do the equivalent by (e.g.) fork and exec in terms of running a sequence of commands, that seek-position is shared by all the inheriting processes. Hence, at the shell prompt, the command:

(prog1; prog2; prog3) > outputfile

creates an output file, then dups the descriptor to the three programs, so that output that prog2 writes goes into the file immediately after the output from prog1, and output from prog3 follows the other two—all because all three separate processes share the same underlying kernel data structure with the same internal loff_t.

The same applies to device driver files. When your read and write functions are called, you receive the "current offset" as provided by the user, and you can (and should) update it as needed ... assuming there is any need (e.g., you want to provide users with the appearance of a regular file, including the fact that seek offsets move as you read and write). If the device has some logical application of the seek offset, you can use that here.

Of course, there's a lot more to device drivers, which is why there are entire book-chapters on this stuff (q.v.). :-)

Solution 2

Torek's answer is excellent. Just adding a bit extra detail/context... From an earlier Linux kernel (2.6.28), here is an example of offset in use in a system call... it copies the offset from user space to a temporary variable before getting into the kernel driver-invocation mechanism, and then copies it back out to the user file. This is how the offset the driver sees is decoupled from the user view of it, and facilitates the situations where offset is NULL in the system call, so no SEGVIO occurs.

SYSCALL_DEFINE4(sendfile64, int, out_fd, int, in_fd, loff_t __user *, offset, size_t, count)
{
    loff_t pos;
    ssize_t ret;

    if (offset) {
        if (unlikely(copy_from_user(&pos, offset, sizeof(loff_t))))
            return -EFAULT;
        ret = do_sendfile(out_fd, in_fd, &pos, count, 0);
        if (unlikely(put_user(pos, offset)))
            return -EFAULT;
        return ret;
    }

    return do_sendfile(out_fd, in_fd, NULL, count, 0);
}
Share:
33,337
Dr.Knowitall
Author by

Dr.Knowitall

I'm a fullstack engineering breaking into the data science world. I'm interested in computer visualization, linguistics processing, machine learning and their applications in quantitative finance.

Updated on December 26, 2020

Comments

  • Dr.Knowitall
    Dr.Knowitall over 3 years

    I'm designing a device driver that simply reads and writes to a character buffer. My question is however regarding the two functions in the file_operations structure read and write. I don't truly understand what loff_t *offp really is. I know that for both the read and write operations that *offp is the file offset meaning the current reading/writing position of the file, however I'm not even sure what it means to write or read to/from a device file.

    From what I gathered, and this is how I am writing and reading from my device is that I create a structure which represents my device which I call my_char_struct which is shown bellow.

    struct my_char_structure{
        struct cdev my_cdev;
        struct semaphore sem;
        char *data;
        ssize_t data_size;
        unsigned int access_key;
        unsigned long size;
    };
    

    This is a static structure that is initialized and pointed to when my driver is insmod as such.

    static dev_t dev_num;
    static struct my_char_structure Dev;
    
    int start_mod(void){
        //Because we are dealing with a fictitious device, I want
        //the driver to create my two devices with arbitrarily 
        //assigned major numbers.
        struct my_char_structure *my_dev = &Dev;
        int err;
    
        alloc_chrdev_region(&dev_num, FIRST_MINOR, COUNT, DEVICE_NAME);
    
        sema_init(&(my_dev->sem),1);
    
        cdev_init(&(my_dev->my_cdev), &fops);
        my_dev->my_cdev.owner = THIS_MODULE;
        my_dev->my_cdev.ops = &fops;// fops is my file operations struct
    
        err = cdev_add(&my_dev->my_cdev, dev_num, COUNT);
        if(err<0)
            printk(KERN_ALERT "There was an error %d.",err);
        printk(KERN_ALERT " insmod to major number %d",MAJOR(dev_num));
    
        return 0;   
    }
    
    module_init(start_mod);
    

    When my device is open, I just make a pointer for the file open to point to that static structure that I've set up during module_init(start_mod) as such ...

    int dev_open(struct inode *in_node, struct file *filp){
        static struct my_char_structure *my_dev;
        my_dev = container_of(in_node->i_cdev, struct my_char_structure, my_cdev);
        printk(KERN_ALERT "The device number is %d",iminor(in_node));
        if(!my_dev)
            printk(KERN_ALERT "something didn't work. my_dev not initialized.");
        filp->private_data = my_dev;
        return 0;
    }
    

    What my read and write methods do is modify that initial structure Dev, that I've pointed to with my open files. Whatever I copy_to_user from my structure is what the user considers been written to the device and whatever I copy_from_user the user thinks they're writing. But beyond changing my initial structure Dev, the idea of file position or offset doesn't make sense unless it refers to a pointer to buffered memory within the kernel for some arbitrary structure or type. Thats the only interpretation that I have for the file offset ... is this correct? Is that what the loff_t *offp here refers to?

    write(struct file *filp, const char __user *buff, size_t count, loff_t *offp)
    read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
    

    (given my understanding is correct) When some file_operation such as read/write is called and I hadn't set *offp personally, what is loff_t *offp initially set to?

    If in the last file_operation offp = some_arbitrary_address(because I told it so), is that what the offp would be set to when this operation is called again?

    What happens if I have other file_opens operations running, will it set to what the last file_operation left it as, or will it keep a tab of which file_open operation it used and replace *offp to what the file_open had it at?

    The concept of a char device is too abstract for me when it seems that the device itself doesn't even store the information like a file should, but rather its the driver that saves the information. I hope I've explained my fogginess and I'll clear up anything that I seem ambiguous about.