Skip to content

Day87 - fasync and SIGIO Internals

Goal

Understand how Linux implements asynchronous event notification using fasync and SIGIO, how user applications enable signal-driven I/O through fcntl(), and how drivers cooperate with the VFS and signal subsystem to notify asynchronous listeners.

What I Learned

Signal-driven I/O

  • Learned why Linux provides signal-driven I/O in addition to blocking I/O and poll() / epoll().
  • Understood the role of SIGIO as an asynchronous notification mechanism.
  • Compared blocking, polling, and signal-driven event models.

fasync Infrastructure

  • Implemented a simplified Linux-style fasync framework.
  • Implemented struct fasync_struct as a linked list of asynchronous listeners.
  • Implemented fasync_helper() to register and unregister listeners.
  • Implemented kill_fasync() to notify all registered listeners.

Driver .fasync() Callback

  • Implemented a driver .fasync() callback.
  • Connected the driver callback to fasync_helper().
  • Learned that drivers only manage the asynchronous listener list, while signal delivery is handled by the kernel signal subsystem.

VFS Integration

  • Added a simplified Linux-style struct file.
  • Added a simplified struct file_operations.
  • Implemented a small VFS helper layer to simulate:
    • fcntl(F_SETOWN)
    • fcntl(F_SETFL | O_ASYNC)
  • Learned how the VFS dispatches asynchronous registration to the driver's .fasync() callback.

Event Notification Flow

  • Simulated a device event that updates driver state.
  • Verified that one device event can notify:
    • Blocking readers through a wait queue.
    • Signal-driven applications through kill_fasync().
  • Reinforced that drivers should notify every supported I/O mechanism without knowing which one the application is using.

Relationship with poll() and epoll()

  • Revisited the role of .poll() as the single readiness interface implemented by drivers.
  • Verified that:
    • poll() and epoll() recheck driver readiness through .poll().
    • SIGIO is delivered independently through kill_fasync().
  • Understood that all three mechanisms observe the same driver state while serving different application models.

Implementation Summary

Implemented a simplified Linux-style asynchronous notification framework including:

  • struct fasync_struct
  • fasync_helper()
  • kill_fasync()
  • Driver .fasync() callback
  • Simplified struct file
  • Simplified struct file_operations
  • Simplified VFS fcntl() helpers
  • Wait queue and asynchronous notification integration
  • Driver .poll() integration with epoll

Labs

Lab1

Implemented the basic fasync framework.

Verified:

  • Listener registration
  • Duplicate registration detection
  • Listener removal
  • Multiple asynchronous listeners

Lab2

Integrated the driver .fasync() callback.

Verified:

  • Driver registration through .fasync()
  • Driver-managed asynchronous listener list
  • Multiple file instances

Lab3

Implemented a simplified VFS fcntl() path.

Verified:

  • F_SETOWN
  • O_ASYNC
  • VFS dispatch to .fasync()

Lab4

Integrated wait queue and asynchronous notification.

Verified:

  • Blocking read
  • wake_up()
  • kill_fasync()
  • Driver event notification

Lab5

Integrated .poll(), epoll(), and SIGIO.

Verified:

  • Driver readiness before and after events
  • Readiness clearing after read()
  • Relationship between poll(), epoll(), and asynchronous notification

Key Takeaways

  • poll(), epoll(), and SIGIO are complementary rather than competing mechanisms.
  • Drivers expose readiness through .poll() and asynchronous notification through .fasync().
  • kill_fasync() complements wake_up() so that one device event can support multiple user-space I/O models.
  • The VFS bridges user-space fcntl() requests to the driver's .fasync() callback.
  • The driver remains the single source of truth for device readiness, while different notification mechanisms expose that state to user space.