diff options
| -rw-r--r-- | cmd/ioworkload/scenario_sysv.go | 171 | ||||
| -rw-r--r-- | cmd/ioworkload/scenarios.go | 2 | ||||
| -rw-r--r-- | integrationtests/ipc_sysv_test.go | 65 |
3 files changed, 238 insertions, 0 deletions
diff --git a/cmd/ioworkload/scenario_sysv.go b/cmd/ioworkload/scenario_sysv.go index 178f04b..c1b917e 100644 --- a/cmd/ioworkload/scenario_sysv.go +++ b/cmd/ioworkload/scenario_sysv.go @@ -96,3 +96,174 @@ func shmRemove(shmid uintptr) error { } return nil } + +// sysvMsgText is the message body copied into and read back out of the SysV +// message queue. Its length (NOT including mtype) is the msgsz passed to +// msgsnd/msgrcv. +var sysvMsgText = []byte("ior-sysv-msg") + +// sysvMsgbuf mirrors the kernel's `struct msgbuf`: an 8-byte signed message +// type (must be > 0 for msgsnd) followed by the message body. The body is sized +// to hold sysvMsgText. +type sysvMsgbuf struct { + mtype int64 + mtext [16]byte +} + +// sysvMsgBasic exercises the SysV message-queue family end-to-end: it creates a +// private queue (msgget IPC_PRIVATE), sends a small message (msgsnd), receives +// it back (msgrcv), and removes the queue (msgctl IPC_RMID). IPC_RMID is always +// issued so no kernel message queue leaks. +func sysvMsgBasic() error { + msqid, err := msgGet() + if err != nil { + return err + } + // Guarantee removal even on a partial failure; an orphaned IPC_PRIVATE + // queue would otherwise leak until reboot. + defer func() { _ = msgRemove(msqid) }() + + if err := msgSend(msqid); err != nil { + return err + } + return msgReceive(msqid) +} + +// msgGet creates a new private SysV message queue and returns its identifier. +func msgGet() (uintptr, error) { + msqid, _, errno := syscall.Syscall( + syscall.SYS_MSGGET, + uintptr(unix.IPC_PRIVATE), + uintptr(unix.IPC_CREAT|0o600), + 0, + ) + if errno != 0 { + return 0, fmt.Errorf("msgget: %w", errno) + } + return msqid, nil +} + +// msgSend enqueues a single message. msgsz counts only the message body +// (mtext), excluding the leading mtype field. +func msgSend(msqid uintptr) error { + buf := sysvMsgbuf{mtype: 1} + copy(buf.mtext[:], sysvMsgText) + _, _, errno := syscall.Syscall6( + syscall.SYS_MSGSND, + msqid, + uintptr(unsafe.Pointer(&buf)), + uintptr(len(sysvMsgText)), + 0, // msgflg: blocking send (the empty queue cannot be full) + 0, 0, + ) + if errno != 0 { + return fmt.Errorf("msgsnd: %w", errno) + } + return nil +} + +// msgReceive dequeues the message previously sent. msgtyp 0 returns the first +// message regardless of type; msgsz must be at least the body size sent. +func msgReceive(msqid uintptr) error { + var buf sysvMsgbuf + _, _, errno := syscall.Syscall6( + syscall.SYS_MSGRCV, + msqid, + uintptr(unsafe.Pointer(&buf)), + uintptr(len(sysvMsgText)), + 0, // msgtyp 0: first message in the queue + 0, // msgflg: blocking receive (a message is already enqueued) + 0, + ) + if errno != 0 { + return fmt.Errorf("msgrcv: %w", errno) + } + return nil +} + +// msgRemove marks the message queue for immediate destruction (IPC_RMID). +func msgRemove(msqid uintptr) error { + _, _, errno := syscall.Syscall(syscall.SYS_MSGCTL, msqid, uintptr(unix.IPC_RMID), 0) + if errno != 0 { + return fmt.Errorf("msgctl IPC_RMID: %w", errno) + } + return nil +} + +// sysvSembuf mirrors the kernel's `struct sembuf` used by semop: +// unsigned short sem_num; short sem_op; short sem_flg; +type sysvSembuf struct { + semNum uint16 + semOp int16 + semFlg int16 +} + +// sysvSemBasic exercises the SysV semaphore family end-to-end: it creates a +// private set of one semaphore (semget IPC_PRIVATE), increments it (semop +// sem_op=+1), then decrements it back to zero (semop sem_op=-1), and removes the +// set (semctl IPC_RMID). The increment happens first so the decrement can never +// block, keeping the scenario hang-free. IPC_RMID is always issued so no kernel +// semaphore set leaks. +func sysvSemBasic() error { + semid, err := semGet() + if err != nil { + return err + } + // Guarantee removal even on a partial failure; an orphaned IPC_PRIVATE + // semaphore set would otherwise leak until reboot. + defer func() { _ = semRemove(semid) }() + + if err := semOp(semid, +1); err != nil { + return err + } + return semOp(semid, -1) +} + +// semGet creates a new private SysV semaphore set with a single semaphore and +// returns its identifier. +func semGet() (uintptr, error) { + semid, _, errno := syscall.Syscall( + syscall.SYS_SEMGET, + uintptr(unix.IPC_PRIVATE), + 1, // nsems: one semaphore in the set + uintptr(unix.IPC_CREAT|0o600), + ) + if errno != 0 { + return 0, fmt.Errorf("semget: %w", errno) + } + return semid, nil +} + +// semOp applies a single operation to semaphore 0 of the set. A positive delta +// increments (never blocks); a negative delta decrements (only safe once the +// value is high enough, which the caller guarantees by incrementing first). +func semOp(semid uintptr, delta int16) error { + sop := sysvSembuf{semNum: 0, semOp: delta, semFlg: 0} + _, _, errno := syscall.Syscall( + syscall.SYS_SEMOP, + semid, + uintptr(unsafe.Pointer(&sop)), + 1, // nsops: one operation + ) + if errno != 0 { + return fmt.Errorf("semop(%d): %w", delta, errno) + } + return nil +} + +// semRemove removes the semaphore set (IPC_RMID). On Linux the union semun +// fourth argument is ignored for IPC_RMID, so passing 0 is safe. +func semRemove(semid uintptr) error { + _, _, errno := syscall.Syscall6( + syscall.SYS_SEMCTL, + semid, + 0, // semnum: ignored for IPC_RMID + uintptr(unix.IPC_RMID), + 0, // arg (union semun): ignored for IPC_RMID + 0, 0, + ) + if errno != 0 { + return fmt.Errorf("semctl IPC_RMID: %w", errno) + } + return nil +} diff --git a/cmd/ioworkload/scenarios.go b/cmd/ioworkload/scenarios.go index c11f25d..4fd2dd0 100644 --- a/cmd/ioworkload/scenarios.go +++ b/cmd/ioworkload/scenarios.go @@ -44,6 +44,8 @@ var scenarios = map[string]func() error{ "fd-from-air-eventfd-users": fdFromAirEventfdUsers, "mq-posix-basic": mqPosixBasic, "sysv-shm-basic": sysvShmBasic, + "sysv-msg-basic": sysvMsgBasic, + "sysv-sem-basic": sysvSemBasic, "mountfs-management": mountfsManagement, "polling-epoll": pollingEpoll, "sleep-syscalls": sleepSyscalls, diff --git a/integrationtests/ipc_sysv_test.go b/integrationtests/ipc_sysv_test.go index 6983c08..d5f4fa9 100644 --- a/integrationtests/ipc_sysv_test.go +++ b/integrationtests/ipc_sysv_test.go @@ -36,3 +36,68 @@ func TestSysVShmBasic(t *testing.T) { assertEventDurationPositive(t, result, ExpectedEvent{Tracepoint: tp, Comm: "ioworkload"}) } } + +// sysvMsgTraceArgs restricts tracing to the SysV message-queue family so the +// captured output is dominated by the lifecycle calls the workload issues. +var sysvMsgTraceArgs = []string{ + "-trace-syscalls", + "msgget,msgsnd,msgrcv,msgctl", +} + +// TestSysVMsgBasic verifies the SysV message-queue family is traced end-to-end. +// The workload runs msgget(IPC_PRIVATE) -> msgsnd -> msgrcv -> msgctl(IPC_RMID); +// each call must appear as an enter event with a positive duration (proving the +// enter/exit pair was correlated). The workload always issues IPC_RMID, so no +// kernel message queue leaks. +func TestSysVMsgBasic(t *testing.T) { + h := newTestHarness(t) + result, pid, err := h.RunWithIorArgs("sysv-msg-basic", defaultDuration, sysvMsgTraceArgs) + if err != nil { + t.Fatalf("run scenario sysv-msg-basic: %v", err) + } + + AssertNoUnexpectedPID(t, result, pid) + AssertNoUnexpectedComm(t, result, "ioworkload") + AssertEventsPresent(t, result, []ExpectedEvent{ + {Tracepoint: "enter_msgget", Comm: "ioworkload", MinCount: 1}, + {Tracepoint: "enter_msgsnd", Comm: "ioworkload", MinCount: 1}, + {Tracepoint: "enter_msgrcv", Comm: "ioworkload", MinCount: 1}, + {Tracepoint: "enter_msgctl", Comm: "ioworkload", MinCount: 1}, + }) + + for _, tp := range []string{"enter_msgget", "enter_msgsnd", "enter_msgrcv", "enter_msgctl"} { + assertEventDurationPositive(t, result, ExpectedEvent{Tracepoint: tp, Comm: "ioworkload"}) + } +} + +// sysvSemTraceArgs restricts tracing to the SysV semaphore family so the +// captured output is dominated by the lifecycle calls the workload issues. +var sysvSemTraceArgs = []string{ + "-trace-syscalls", + "semget,semop,semctl", +} + +// TestSysVSemBasic verifies the SysV semaphore family is traced end-to-end. The +// workload runs semget(IPC_PRIVATE) -> semop(+1) -> semop(-1) -> +// semctl(IPC_RMID); each distinct syscall must appear as an enter event with a +// positive duration (proving the enter/exit pair was correlated). The workload +// always issues IPC_RMID, so no kernel semaphore set leaks. +func TestSysVSemBasic(t *testing.T) { + h := newTestHarness(t) + result, pid, err := h.RunWithIorArgs("sysv-sem-basic", defaultDuration, sysvSemTraceArgs) + if err != nil { + t.Fatalf("run scenario sysv-sem-basic: %v", err) + } + + AssertNoUnexpectedPID(t, result, pid) + AssertNoUnexpectedComm(t, result, "ioworkload") + AssertEventsPresent(t, result, []ExpectedEvent{ + {Tracepoint: "enter_semget", Comm: "ioworkload", MinCount: 1}, + {Tracepoint: "enter_semop", Comm: "ioworkload", MinCount: 1}, + {Tracepoint: "enter_semctl", Comm: "ioworkload", MinCount: 1}, + }) + + for _, tp := range []string{"enter_semget", "enter_semop", "enter_semctl"} { + assertEventDurationPositive(t, result, ExpectedEvent{Tracepoint: tp, Comm: "ioworkload"}) + } +} |
