diff --git a/kernel/include/sched.h b/kernel/include/sched.h
index db0035c69be6dc9938a08a6aab84c6149ff28902..9f5647a751802505c96e7fe4563ad739ad825489 100644
--- a/kernel/include/sched.h
+++ b/kernel/include/sched.h
@@ -33,8 +33,11 @@ struct pcb_t
     uintptr_t user_sp;
     uintptr_t kernel_sp_base;
     uintptr_t *pgdir;
-    pid_t pid;
-    pid_t ppid;
+    pid_t pid;  // process id
+    pid_t ppid; // parent process id
+    pid_t tid;  // thread id
+    pid_t pgid; // process group id
+    pid_t sid;  // session id
     proc_status_t status;
     int exit_status;
 
@@ -66,6 +69,8 @@ struct pcb_t
     // real, effective, and saved user/group IDs
     uid_t ruid, euid, suid;
     gid_t rgid, egid, sgid;
+    // supplementary group IDs
+    std::list<gid_t> supgids;
 
     char name[16]; // for debug
 };
@@ -109,10 +114,17 @@ uid_t getuid();
 uid_t geteuid();
 gid_t getgid();
 gid_t getegid();
+pid_t gettid();
 pcb_t *get_current_proc();
 clock_t do_times(tms *buf);
 int do_kill(pid_t pid, int signum);
 int do_tkill(pid_t pid, int signum);
 int do_tgkill(pid_t tgid, pid_t pid, int signum);
+int setpgid(pid_t pid, pid_t pgid);
+pid_t getpgid(pid_t pid);
+pid_t getsid(pid_t pid);
+pid_t setsid();
+int getgroups(int size, gid_t *list);
+int setgroups(size_t size, const gid_t *list);
 
 #endif
\ No newline at end of file
diff --git a/kernel/scheduler/sched.cpp b/kernel/scheduler/sched.cpp
index fa7701c54db3aecfe27d9442108d0eff9b53189a..2659b6bd0a7b4da3672232dc364a879bd809f9ef 100644
--- a/kernel/scheduler/sched.cpp
+++ b/kernel/scheduler/sched.cpp
@@ -153,6 +153,9 @@ void init_pcb(hartid_t hartid) {
     proc->user_sp = alloc_page(1, proc->memory_list) + PAGE_SIZE;
     proc->pgdir = kernel_pgdir;
     proc->ppid = 0;
+    proc->tid = 0;
+    proc->pgid = 0;
+    proc->sid = 0;
     proc->status = RUNNING;
 
     // fs
@@ -171,6 +174,8 @@ void init_pcb(hartid_t hartid) {
     proc->rgid = 0;
     proc->egid = 0;
     proc->sgid = 0;
+    // supplementary group list = root
+    proc->supgids.push_back(0);
 }
 
 void init_pcb_cwd() {
@@ -188,6 +193,9 @@ pid_t fork(Context *c, void *stack)
     pcb_t *pproc = get_current_proc();
     pcb_t *proc = alloc_pcb();
     proc->ppid = pproc->pid;
+    proc->tid  = proc->pid;  // use pid as tid
+    proc->pgid = proc->pid;  // use pid as pgid
+    proc->sid  = pproc->sid; // same session with parent
     strncpy(proc->name, pproc->name, 16);
 
     proc->pgdir = fork_pgdir(pproc->pgdir, proc->memory_list);
@@ -229,6 +237,10 @@ pid_t fork(Context *c, void *stack)
     proc->rgid = pproc->rgid;
     proc->egid = pproc->egid;
     proc->sgid = pproc->sgid;
+    // supplementary groups = parent's
+    for (auto gid : pproc->supgids) {
+        proc->supgids.push_back(gid);
+    }
 
     proc->status = READY;
     ready_queue.push_back(proc);
@@ -301,7 +313,11 @@ pid_t kernel_execve(const char *pathname, char *const argv[], char *const envp[]
     pcb_t *pproc = get_current_proc();
     pcb_t *proc = alloc_pcb();
     strncpy(proc->name, pathname, 16);
-    proc->ppid = 0;
+    // proc->pid should be 1, pproc->pid should be 0
+    proc->ppid = pproc->pid;
+    proc->tid  = proc->pid;
+    proc->pgid = proc->pid;
+    proc->sid  = proc->pid;
 
     proc->pgdir = reinterpret_cast<uintptr_t*>(alloc_page(1, proc->memory_list));
     memcpy(proc->pgdir, kernel_pgdir, PAGE_SIZE);
@@ -359,6 +375,18 @@ pid_t kernel_execve(const char *pathname, char *const argv[], char *const envp[]
     proc->sched_time = 0;
     proc->times = {0};
 
+    // real, effective, saved uid/gid = parent's
+    proc->ruid = pproc->ruid;
+    proc->euid = pproc->euid;
+    proc->suid = pproc->suid;
+    proc->rgid = pproc->rgid;
+    proc->egid = pproc->egid;
+    proc->sgid = pproc->sgid;
+    // supplementary groups = parent's
+    for (auto gid : pproc->supgids) {
+        proc->supgids.push_back(gid);
+    }
+
     proc->status = READY;
     ready_queue.push_back(proc);
 
@@ -509,6 +537,10 @@ gid_t getegid() {
     return get_current_proc()->egid;
 }
 
+pid_t gettid() {
+    return get_current_proc()->tid;
+}
+
 pcb_t *get_current_proc() {
     return hart[get_hartid()].proc;
 }
@@ -554,3 +586,52 @@ int do_tgkill(pid_t tgid, pid_t pid, int signum)
 {
     return do_kill(pid, signum); // FIXME
 }
+
+int setpgid(pid_t pid, pid_t pgid) {
+    // FIXME: source and destination process groups must in the same session
+    pcb_t *proc = pid == 0 ? get_current_proc() : find_pcb(pid);
+    if (proc == nullptr) return -1;
+    proc->pgid = pgid == 0 ? proc->pid : pgid;
+    return 0;
+}
+
+pid_t getpgid(pid_t pid) {
+    pcb_t *proc = pid == 0 ? get_current_proc() : find_pcb(pid);
+    if (proc == nullptr) return -1;
+    return proc->pgid;
+}
+
+pid_t getsid(pid_t pid) {
+    pcb_t *proc = pid == 0 ? get_current_proc() : find_pcb(pid);
+    if (proc == nullptr) return -1;
+    return proc->sid;
+}
+
+pid_t setsid() {
+    pcb_t *proc = get_current_proc();
+    if (proc->pgid == proc->pid) return -1; // already process group leader
+    proc->sid  = proc->pid;
+    proc->pgid = proc->pid;
+    return proc->sid;
+}
+
+int getgroups(int size, gid_t *list) {
+    pcb_t *proc = get_current_proc();
+    int n = proc->supgids.size();
+    if (size == 0) return n;
+    if (size < n) return -1;
+    for (auto gid: proc->supgids) {
+        *list++ = gid;
+    }
+    return n;
+}
+
+int setgroups(size_t size, const gid_t *list) {
+    if (size != 0 && list == nullptr) return -1;
+    pcb_t *proc = get_current_proc();
+    proc->supgids.clear();
+    for (size_t i = 0; i < size; i++) {
+        proc->supgids.push_back(list[i]);
+    }
+    return 0;
+}
diff --git a/kernel/syscall/syscall.cpp b/kernel/syscall/syscall.cpp
index 42d1c105aca50dd079636da4522f6658f495b238..c4a149ddad61fc5dc0ff0fec4e6740e00cd660c9 100644
--- a/kernel/syscall/syscall.cpp
+++ b/kernel/syscall/syscall.cpp
@@ -766,33 +766,27 @@ static void sys_times(Context *c) {
 }
 
 static void sys_setpgid(Context *c) {
-    fmt::err("unimplemented syscall setpgid\n");
-    sys_unimplemented(c);
+    a0 = setpgid(a0, a1);
 }
 
 static void sys_getpgid(Context *c) {
-    fmt::err("unimplemented syscall getpgid\n");
-    sys_unimplemented(c);
+    a0 = getpgid(a0);
 }
 
 static void sys_getsid(Context *c) {
-    fmt::err("unimplemented syscall getsid\n");
-    sys_unimplemented(c);
+    a0 = getsid(a0);
 }
 
 static void sys_setsid(Context *c) {
-    fmt::err("unimplemented syscall setsid\n");
-    sys_unimplemented(c);
+    a0 = setsid();
 }
 
 static void sys_getgroups(Context *c) {
-    fmt::err("unimplemented syscall getgroups\n");
-    sys_unimplemented(c);
+    a0 = getgroups(a0, (gid_t *) a1);
 }
 
 static void sys_setgroups(Context *c) {
-    fmt::err("unimplemented syscall setgroups\n");
-    sys_unimplemented(c);
+    a0 = setgroups(a0, (const gid_t *) a1);
 }
 
 static void sys_uname(Context *c) {
@@ -878,8 +872,7 @@ static void sys_getegid(Context *c) {
 }
 
 static void sys_gettid(Context *c) {
-    fmt::err("unimplemented syscall gettid\n");
-    sys_unimplemented(c);
+    a0 = gettid();
 }
 
 static void sys_sysinfo(Context *c) {
diff --git a/libs/include/list.h b/libs/include/list.h
index 67e8c15a916283cc0a8cc6d0a3e203a62b388ee2..55a901a3c0af5105ee38b70075797a81486f6dc9 100644
--- a/libs/include/list.h
+++ b/libs/include/list.h
@@ -2,6 +2,7 @@
 #define __LIST_H__
 
 #include <mem.h>
+#include <stddef.h>
 
 namespace std
 {
@@ -37,16 +38,19 @@ class list
 private:
     ListNode_t<T> head_guard;
     ListNode_t<T> *head, *tail;
+    size_t __size;
 
 public:
     typedef T               value_type;
     typedef T*              pointer;
     typedef T&              reference;
     typedef ListIterator<T> iterator;
+    typedef size_t          size_type;
 
     list() {
         head_guard.prev = head_guard.next = nullptr;
         head = tail = &head_guard;
+        __size = 0;
     }
 
     iterator begin() { return iterator(head); }
@@ -70,6 +74,7 @@ public:
             tail->prev = p;
             p->next = tail;
         }
+        ++ __size;
     }
 
     void pop_front()
@@ -81,6 +86,7 @@ public:
         head->prev = nullptr;
         if (_head != &head_guard)
             destroy<ListNode_t<T>>(_head);
+        -- __size;
     }
 
     iterator erase(iterator __position)
@@ -102,12 +108,15 @@ public:
             iterator ret(p->next);
             if (p != &head_guard)
                 destroy<ListNode_t<T>>(p);
+            -- __size;
             return ret;
         }
     }
 
     reference front() { return head->value; }
     bool empty() const { return head == tail; }
+    size_type size() const { return __size; }
+    void clear() { while (!empty()) pop_front(); }
 };
 
 }