1
votes

I have a question about Linux device file and drivers, with my current understanding as the following:

  1. When a user calls open() on some device file, at some point of time kernel will ask inode of the device file for the read()/write() function.

  2. The device file was created by udev earlier, based on devices in /sys folder, meaning the inode of the device file should have i_fop field pointed to functions that know how to talk to the device, such as read/write/mmap/ioctl/poll. And that means each device file's inode->i_fop field should point to a different file_operations struct.

If so, the device driver will be providing these read()/write() functions, maybe the complete file_operations struct, including read/write/mmap/ioctl, etc.

now , ULK says (in the description of open() syscall for device files) "sets the i_fop field of the inode object to the address of either the def_blk_fops or the def_chr_fops file operation table, according to the type of device file." And that means all block device files have the same read()/write() function, but then how can a user talk to different devices?

I also checked the device_driver struct, and indeed, there are no place to store file access functions, so how exactly an open() syscall performs its job using a device specific driver then? And where do device specific operation function live, if not in device_driver?

2
"How does Linux link device file with device driver?" - By major and minor numbers. Actually, any device file contains only these numbers. Only when device file is opened, Linux kernel checks whether a device with such numbers is registered, and calls corresponded functions for that device. - Tsyvarev
@Tsyvarev thanks, so what does device_driver provide in this process, since the cdev struct already have file_operations? - QnA
If you look into device_driver structure's definition you will find that this structure describes completely different things than the file operations. It has e.g. probe function which binds the device with a driver. - Tsyvarev

2 Answers

3
votes

The following applies to opening character special devices.

When the file is opened, the i_fop pointer from the inode object gets copied to the f_op pointer of the file object. For character special devices, this points to def_chr_fops. def_chr_fops.open points to chrdev_open, so chrdev_open(inode, filp) is called when opening any character special device.

chrdev_open looks through its set of registered struct cdev objects which map the inode's major/minor numbers (combined into a dev_t number) to a particular registered struct cdev. If no matching struct cdev is found, it returns -ENXIO. Otherwise, it replaces the f_op pointer in the file object with the ops pointer from the struct cdev, which was set by the driver for the character special device.

If the file object's f_op->open is non-null, it is called and its return value is returned by chrdev_open. Otherwise, no special "open" handling is required for this character special device and 0 is returned.

If chrdev_open returns 0, the file object is in the "open" state and its f_op pointer points to the driver-specific file operations. The open syscall will eventually return a file descriptor. If chrdev_open returns a negative errno value, the file object will be destroyed, the open syscall will return -1 and errno will be set according to the return value from chrdev_open.

1
votes

Short answer, it knows which open function to call ultimately based on the major and minor numbers. You provide these or get ones automatically generated for you when you register your driver, whether character, block or tty.

What additionally happens with udev or systemd or mdev is that a device gets created under /dev automatically. There are cases with embedded distros (e.g. buildroot) and character devices where this won't happen automatically and you need to manually do it. I would suggest checking "Linux Device Drivers, 3rd edition, Chapter 3: Char Drivers" for more details.