Skip to content

Day85 - poll() Internals

Goal

Understand how Linux poll() is built on top of wait queues and how a driver exposes event readiness through its poll callback.

Topics Covered

  • poll()
  • poll_wait()
  • poll_table
  • Driver poll callback
  • Event mask
  • POLLIN
  • Event-driven I/O
  • Relationship between poll and wait queues
  • list_head as a shared kernel-style list primitive

Labs Completed

Lab1 - Poll Table and Wait Queue Mapping

Built a simplified poll table.

Key points:

  • poll_table records wait queues monitored by one poll operation.
  • poll_table_entry stores a pointer to a wait_queue_head.
  • poll_wait() registers a wait queue into the poll table.
  • poll_wait() does not sleep.

Lab2 - Driver poll Callback

Implemented a simulated driver poll callback.

Flow:

driver_poll()
    |
    +-- poll_wait()
    |
    +-- check device state
    |
    +-- return event mask

Validation:

  • No data returns 0.
  • Data ready returns POLLIN.

Lab3 - Userspace poll Flow

Implemented a simplified userspace poll() flow.

Flow:

user_poll()
    |
    +-- poll_table_init()
    |
    +-- driver_poll()
    |
    +-- poll_table_destroy()

Validation:

  • No event prints a waiting message and returns 0.
  • Ready event returns POLLIN.

Lab4 - Blocking poll Simulation

Implemented a blocking poll simulation with two threads.

Flow:

poll thread
    |
    +-- user_poll()
    |
    +-- driver_poll()
    |
    +-- no event
    |
    +-- wait_event()
    |
    +-- wake_up()
    |
    +-- driver_poll()
    |
    +-- return POLLIN
    |
    +-- read()

Producer flow:

producer thread
    |
    +-- data_ready = true
    |
    +-- wake_up()

Final validation log:

[POLL] user poll start
[POLL] no event, sleep
[DRV] data ready
mask=0x00000001
[DRV] read data

Implementation Notes

Shared list utility

Added a simplified Linux-style list_head implementation.

Main APIs:

  • INIT_LIST_HEAD()
  • list_add()
  • list_add_tail()
  • list_del()
  • list_empty()
  • list_entry()
  • list_first_entry()
  • list_for_each()
  • list_for_each_entry()
  • list_for_each_entry_safe()

Key concept:

  • Linux list_head is a circular doubly linked list.
  • The list head is a sentinel node, not a data node.
  • list_del() can update head->next or head->prev automatically because the head participates in the circular list.

Shared wait queue utility

Moved wait queue logic into a reusable utils/waitqueue/ module.

Main concepts:

  • wait_queue_head
  • wait_queue_entry
  • task state
  • prepare_to_wait()
  • finish_wait()
  • wait_event()
  • wake_up()

Important design decision:

  • wake_up() only wakes waiting entries.
  • finish_wait() restores task state and removes the wait queue entry.
  • Waiting loops must re-check the condition after wakeup.

Shared poll utility

Added a simplified utils/poll/ module.

Main concepts:

  • poll_table
  • poll_table_entry
  • poll_wait()
  • event masks

Important design decision:

  • poll_wait() only registers a wait queue.
  • It does not sleep.
  • The driver poll callback decides readiness by returning an event mask.

Key Takeaways

  • poll() is an event readiness mechanism, not a data transfer mechanism.
  • read() consumes data; poll() reports whether data can be read.
  • Driver poll callbacks usually follow this pattern:
poll_wait()
check device state
return mask
  • poll_wait() connects the current poll operation to the driver's wait queue.
  • A wakeup only means the condition may have changed.
  • The waiting side must always re-check the condition.

Next Step

Continue with epoll at a conceptual level and compare it with poll.

Focus areas:

  • Why poll() scales poorly with many file descriptors
  • How epoll avoids scanning all descriptors every time
  • Why drivers still only implement the normal poll callback