You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(stack-coexist): route readv/writev/ioctl/dup to kernel stack under FF_KERNEL_COEXIST
Add ff_host_readv/writev/ioctl/dup/dup2 bridges and route managed kernel fds through them (ioctl uses the raw Linux request). select/poll stay kernel-fd-unaware by design (use epoll/kqueue). All macro-gated, macro-off byte-identical; adds cmocka tests and syncs zh/en spec + three-layer docs.
- A kernel-stack fd is returned as `host_fd + 0x40000000` (`FF_KERNEL_FD_BASE`), which never collides with FreeBSD fds (`< 65536`); entries route such fds to thin `ff_host_*` host-libc bridges.
313
313
- The F-Stack ↔ host fd pairing is held in `ff_native_fd_map`; `ff_epoll_*` lazily pairs a host `epoll` per kqueue for unified event delivery. A dual-built `AF_INET6` socket gets `IPV6_V6ONLY=1` on its host counterpart (`ff_host_set_v6only`, R9) so v4+v6 coexist on the same port (fixes the prior `-DINET6``errno=98` startup failure).
314
314
-**R9** extends unified events to the native `ff_kqueue`/`ff_kevent` interface (shared `ff_epoll_host_ep`): `ff_kevent` registers a kernel/dual-stack fd's `EVFILT_READ/WRITE` into the kqueue-paired host epoll and synthesizes `struct kevent` (`ident`=app-side fd) from a non-blocking host-epoll poll before merging F-Stack events — a pure-kqueue app (`example/main.c`) now reaches the kernel-side listener (`curl 127.0.0.1:80`=200, was 000).
315
-
- When the macro is off the library is byte-for-byte identical to the pure-F-Stack build. Known limitations: `ff_readv`/`ff_writev`/`ff_ioctl` are not yet kernel-routed; kernel fds via kqueue support `EVFILT_READ/WRITE` only. See `docs/kernel_event_support_spec/`.
315
+
-**R10** completes residual-entry kernel-fd routing: `ff_readv`/`ff_writev` kernel fd via `ff_host_readv/writev` (mimic read/write); `ff_ioctl` kernel fd uses the **raw Linux request** straight to `ff_host_ioctl` (dual-stack fd same-driver NOT implemented, only the encode kernel fd routed); `ff_dup`→`ff_host_dup`+encode, `ff_dup2` both-kernel→`ff_host_dup2`+encode / cross-stack rejected `errno=EINVAL`.
316
+
- When the macro is off the library is byte-for-byte identical to the pure-F-Stack build. Known limitations: kernel fds via kqueue support `EVFILT_READ/WRITE` only; `ff_select` (encode kernel fd ≫ `FD_SETSIZE` hard limit) and `ff_poll` (conservatively not implemented) do not support kernel-fd coexistence — use `ff_epoll_*`/`ff_kqueue` for kernel-fd multiplexing. See `docs/kernel_event_support_spec/`.
int du = ff_socket(AF_INET, SOCK_STREAM, 0); // dual-created (default)
187
187
```
188
188
189
-
A kernel-stack fd is returned as `host_fd + 0x40000000` and is accepted transparently by all `ff_*` calls (forwarded to `ff_host_*` bridges), including `ff_epoll_*` and — since **R9** — `ff_kqueue`/`ff_kevent`: each kqueue lazily pairs one host epoll (shared `ff_epoll_host_ep`), `ff_kevent` registers a kernel/dual-stack fd's `EVFILT_READ/WRITE` there and synthesizes `struct kevent` (`ident`=app-side fd) from a non-blocking host-epoll poll before merging F-Stack events — so a pure-kqueue app (`example/main.c`) reaches the kernel-side listener (`curl 127.0.0.1:80`=200). A dual-built `AF_INET6` socket gets `IPV6_V6ONLY=1` on its host counterpart so a `-DINET6` build starts with v4+v6 on the same port. Off by default → byte-for-byte identical build. Known limitations: `ff_readv` / `ff_writev` / `ff_ioctl` are not yet kernel-routed; kernel fds via kqueue support `EVFILT_READ/WRITE` only. See `docs/kernel_event_support_spec/`.
189
+
A kernel-stack fd is returned as `host_fd + 0x40000000` and is accepted transparently by all `ff_*` calls (forwarded to `ff_host_*` bridges), including `ff_epoll_*` and — since **R9** — `ff_kqueue`/`ff_kevent`: each kqueue lazily pairs one host epoll (shared `ff_epoll_host_ep`), `ff_kevent` registers a kernel/dual-stack fd's `EVFILT_READ/WRITE` there and synthesizes `struct kevent` (`ident`=app-side fd) from a non-blocking host-epoll poll before merging F-Stack events — so a pure-kqueue app (`example/main.c`) reaches the kernel-side listener (`curl 127.0.0.1:80`=200). A dual-built `AF_INET6` socket gets `IPV6_V6ONLY=1` on its host counterpart so a `-DINET6` build starts with v4+v6 on the same port. Since **R10**, `ff_readv`/`ff_writev`/`ff_ioctl`/`ff_dup`/`ff_dup2` also support kernel-fd routing (`ff_readv/writev` via `ff_host_readv/writev`; `ff_ioctl` kernel fd uses the raw Linux request straight to `ff_host_ioctl`, dual-stack fd same-driver NOT implemented; `ff_dup2` cross-stack rejected `errno=EINVAL`). Off by default → byte-for-byte identical build. Known limitations: kernel fds via kqueue support `EVFILT_READ/WRITE` only; `ff_select` (encode kernel fd ≫ `FD_SETSIZE` hard limit) / `ff_poll` (conservatively not implemented) do not support kernel-fd coexistence — use `ff_epoll_*`/`ff_kqueue`. See `docs/kernel_event_support_spec/`.
-**`ff_epoll.c` (289 L)**: `ff_epoll_pairs[64]` lazily pairs a host `epoll` per kqueue; `ff_epoll_wait` polls host epoll (non-blocking) then merges kqueue events (single-threaded, no lock). `ff_epoll_host_ep` is shared (promoted from `static`) so the R9 kqueue path reuses the same pairing table.
569
569
-**R9 kqueue/kevent coexistence (`ff_syscall_wrapper.c`)**: `ff_kqueue`/`ff_kevent` mirror the epoll path — register a kernel/dual-stack fd's `EVFILT_READ/WRITE` into the kqueue-paired host epoll (`ff_host_kqueue_ctl`), synthesize `struct kevent` from a non-blocking `ff_host_kqueue_poll` (`ident`=app-side fd, `EV_EOF`↔`EPOLLHUP|ERR`), then merge `ff_kevent_do_each` F-Stack events. Fixes the `example/main.c` kqueue model (kernel-side `curl 127.0.0.1:80`=200, was 000). Kernel fds: `EVFILT_READ/WRITE` only.
570
570
-**R9 IPv6**: a dual-built `AF_INET6` socket gets `IPV6_V6ONLY=1` on its host counterpart (`ff_host_set_v6only`), so a `-DINET6` build starts with v4+v6 on the same port (fixes the prior `errno=98 EADDRINUSE`).
571
-
-**`ff_syscall_wrapper.c`**: `ff_socket` dual-create + per-entry kernel-fd routing. Not routed: `ff_readv`/`ff_writev`/`ff_ioctl`.
571
+
-**R10 residual-entry coexistence (`ff_syscall_wrapper.c`)**: `ff_ioctl` (L1067) kernel fd uses the **raw Linux request** straight to `ff_host_ioctl` (NOT via `linux2freebsd_ioctl`; dual-stack fd same-driver NOT implemented, only the encode kernel fd routed); `ff_readv` (L1189)/`ff_writev` (L1251) kernel fd via `ff_host_readv/writev` (mimic read/write, connection fds single-stack hot path); `ff_dup` (L2130) kernel fd → `ff_host_dup`+encode; `ff_dup2` (L2156) both-kernel → `ff_host_dup2`+encode / cross-stack rejected `errno=EINVAL`. Known limits: `ff_select` (encode kernel fd ≫ `FD_SETSIZE` hard limit) / `ff_poll` (conservatively not implemented) do not support kernel-fd coexistence — use `ff_epoll_*`/`ff_kqueue`.
@@ -436,8 +436,9 @@ By default every socket lives purely in the F-Stack user-space stack. The option
436
436
- **Managed kernel-fd space.** A kernel-stack fd is handed to the application as `host_fd + FF_KERNEL_FD_BASE` (`0x40000000`), far above the maximum FreeBSD fd (`kern.maxfiles <= 65536`), so the two fd ranges never collide. F-Stack entry points recognise such an fd and route it to a thin host-libc bridge; the default F-Stack path is left untouched.
437
437
- **Dual-stack fd pairing.** For a dual-created socket the F-Stack fd ↔ host fd pairing is tracked in `ff_native_fd_map`, so control/data operations drive both stacks where it matters (e.g. `bind`/`listen` on both, `shutdown`/`close` on both). For a dual-created `AF_INET6` socket the host counterpart is set to `IPV6_V6ONLY=1` (`ff_host_set_v6only`, R9) so it coexists with the same-port host IPv4 socket; this fixes the prior `-DINET6` startup failure (`ff_bind` `errno=98 EADDRINUSE`).
438
438
- **Unified event loop.** `ff_epoll_*` lazily pairs one host `epoll` fd per kqueue, so kernel-fd and F-Stack events are delivered from the single `ff_epoll_wait()` the application already runs. **R9** extends the same mechanism to the native `ff_kqueue`/`ff_kevent` interface (shared `ff_epoll_host_ep` pairing): `ff_kevent` registers a kernel/dual-stack fd's `EVFILT_READ/WRITE` into the kqueue-paired host epoll and synthesizes `struct kevent` (`ident`=the app-side fd) from a non-blocking host-epoll poll before merging F-Stack kqueue events — so a pure-kqueue app (`example/main.c`) now reaches the kernel-side listener (measured `curl 127.0.0.1:80` = 200 size=438, was 000).
439
+
- **R10 residual-entry coexistence.** `ff_readv`/`ff_writev` kernel fd via `ff_host_readv/writev` (mimic read/write); `ff_ioctl` kernel fd uses the **raw Linux request** straight to `ff_host_ioctl` (dual-stack fd same-driver NOT implemented, only the encode kernel fd routed); `ff_dup`→`ff_host_dup`+encode, `ff_dup2` both-kernel→`ff_host_dup2`+encode / cross-stack rejected `errno=EINVAL`.
439
440
440
-
**Known limitations (this release):** `ff_readv` / `ff_writev` / `ff_ioctl` are not yet routed to the kernel stack — use `ff_read` / `ff_write` for dual-stack fds; kernel fds via kqueue support `EVFILT_READ/WRITE` only. Full design, test, and review-gate record: `docs/kernel_event_support_spec/` (+ `zh_cn/`).
441
+
**Known limitations (this release):** kernel fds via kqueue support `EVFILT_READ/WRITE` only; `ff_select` (encode kernel fd ≥ `0x40000000` ≫ `FD_SETSIZE`(1024), hard limit) and `ff_poll` (conservatively not implemented) do not support kernel-fd coexistence — use `ff_epoll_*` / `ff_kqueue` for kernel-fd multiplexing. Full design, test, and review-gate record: `docs/kernel_event_support_spec/` (+ `zh_cn/`).
Copy file name to clipboardExpand all lines: docs/F-Stack_Architecture_Layer2_Interface_Specification.md
+3-1Lines changed: 3 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -635,10 +635,12 @@ int du = ff_socket(AF_INET, SOCK_STREAM, 0); // dual-created (def
635
635
636
636
Priority: per-socket marker > `config.ini [stack] kernel_coexist` > F-Stack. When the macro is not compiled in, the markers are undefined and `ff_socket` behaves exactly as before.
637
637
638
-
**Transparent routing.** All standard `ff_*` calls accept a managed kernel fd transparently: `ff_bind/listen/connect/accept[4]/close/read/write/recv*/send*/sendmsg/recvmsg/getpeername/getsockname/shutdown/setsockopt/getsockopt/fcntl`, and `ff_epoll_ctl/wait`. Internally each kernel fd is forwarded to a thin `ff_host_*` host-libc bridge (`lib/ff_host_interface.c`). **Not yet routed (known limitation):**`ff_readv` / `ff_writev` / `ff_ioctl`— use `ff_read` / `ff_write` for kernel/dual-stack fds. Full design and tests: `docs/kernel_event_support_spec/`.
638
+
**Transparent routing.** All standard `ff_*` calls accept a managed kernel fd transparently: `ff_bind/listen/connect/accept[4]/close/read/write/recv*/send*/sendmsg/recvmsg/getpeername/getsockname/shutdown/setsockopt/getsockopt/fcntl`, and `ff_epoll_ctl/wait`. Internally each kernel fd is forwarded to a thin `ff_host_*` host-libc bridge (`lib/ff_host_interface.c`). **Since R10 also accepted transparently:**`ff_readv` / `ff_writev` / `ff_ioctl`/ `ff_dup` / `ff_dup2` (kernel fd via the matching `ff_host_*` bridge; `ff_ioctl` uses the raw Linux request; `ff_dup2` cross-stack rejected `errno=EINVAL`). Full design and tests: `docs/kernel_event_support_spec/`.
639
639
640
640
**R9 — kqueue/kevent coexistence + IPv6.** The unified-event support now also covers the native `ff_kqueue` / `ff_kevent` interface (previously only `ff_epoll_*`): each kqueue lazily pairs one host epoll (shared `ff_epoll_host_ep`, reusing the `ff_epoll_pairs` table). `ff_kevent` registers a kernel/dual-stack fd's `EVFILT_READ/WRITE` into that host epoll (kernel-only changes are not forwarded to the F-Stack kqueue), and on wait it synthesizes `struct kevent` (`ident`=the app-side fd, `EV_EOF`↔`EPOLLHUP|ERR`) from a non-blocking host-epoll poll before merging F-Stack kqueue events. This makes a pure-kqueue application (e.g. `example/main.c`) reach the kernel-side listener — measured `curl 127.0.0.1:80` = 200 size=438. Kernel fds via kqueue support `EVFILT_READ/WRITE` only. On the IPv6 side, a dual-built `AF_INET6` socket has its host counterpart set to `IPV6_V6ONLY=1` (`ff_host_set_v6only`) so a `-DINET6` build starts cleanly with v4+v6 on the same port (fixes the prior host-IPv6 `errno=98 EADDRINUSE`).
641
641
642
+
**R10 — residual-entry coexistence.**`ff_readv`/`ff_writev` (kernel fd → `ff_host_readv/writev`, mimic read/write, connection fds single-stack hot path), `ff_ioctl` (kernel fd uses the **raw Linux request** straight to `ff_host_ioctl`, NOT via `linux2freebsd_ioctl`; dual-stack fd same-driver NOT implemented, only the encode kernel fd is routed), `ff_dup` (kernel fd → `ff_host_dup`+encode), `ff_dup2` (both-kernel → `ff_host_dup2`+encode; cross-stack rejected `errno=EINVAL`). Adds 5 host bridges `ff_host_readv/writev/ioctl/dup/dup2`. Known limitation: `ff_select` (encode kernel fd ≫ `FD_SETSIZE` hard limit) / `ff_poll` (conservatively not implemented) do not support kernel-fd coexistence — use `ff_epoll_*`/`ff_kqueue`.
0 commit comments