Hi,
As someone with a very advanced custom Unix, beware some of the advise in this thread.
Linux is an old operating system and the i386 ABI is full of compatibility and old practices. Following old Linux practices may not be a good idea. There's also do need to pick 0x80 as the syscall interrupt as unlike glauxosdever suggests, no third party applications actually do raw syscalls. They just use the libc standard API. (A few times, I've seen bad software use sys/syscall.h to do raw syscalls, that is worrying. That software is broken anyway, don't try to support it.)
The Linux syscall ABI for i386 is also ancient by design. POSIX has a lot of modern facilities that are superior to the traditional ones. For instance, open is dominated by the new openat system call and gettimeofday with clock_gettime. All the traditional filesystem opening system calls have *at variants now. Those are crucial from a security and reliability perspective and fits in well with the Unix design. Don't implement the older facilities like open as a system call, but have open in the libc call openat. This is much simpler and cleaner. Newer Linux ABI archs do this, too.
See my
system call list. It's pretty big and has a few non-standard extensions. But I basically need all of those system calls now. Notice how none of the traditional standard interfaces with more powerful replacements are there.
However, all that said, Linux ABI compatibility may be desirable. It's not desirable on its own merits as the ABI has issues, but if you want to make a real Unix operating system, you are going to need a libc. The best option is to write one yourself as you can improve the ABI as I do, but that is a lot of work. A perfectly viable alternative is to implement the Linux ABI and then use the musl libc, which is really great. That'll save you effort, sacrifice a little quality, but get something good that works with less effort. Don't do newlib for quality reasons.
What system calls do you need first? Well, I can't tell you, but your test programs for your OS will. If you want a ls, you are going to need a syscall to load a program (execve), you will need openat to open the directory, fstat to see if what you opened was actually a dir (or do O_DIRECTORY in your openat), a read-directory-entries system call (there's no standard one, readdir(3) is a libc abstraction), then a write system call to write to fd 1 (stdout), and a close system call to close the directory, and _exit to kill the process at the end. My point is that you think of something you want to runm and then find out what it needs. I do the same with third party software, I try to compile it and see what it errors on, then implement that.
You don't need any special system calls for being multi threaded, except obviously a system call to create threads and one for them to exit, and optionally a kernel assisted futex syscall for efficient sleeping. In my OS, to create threads, I just allocate with mmap a new stack and per-thread structure for it in user-space, use tfork to create a thread in this address space with the specified registers, and then that happens. The kernel needs to keep track of very little and doesn't care at all what the threads do in user-space. It's pretty beautiful.
malloc isn't a syscall. mmap is. (Don't implement brk/sbrk, do mmap!). mmap can be used to allocate memory by MAP_ANONYMOUS, MAP_PRIVATE, PROT_READ|PROT_WRITE so it's process private and unbacked by a file. malloc and free is just a thread safe libc facility that maintain a general purpose allocation data structure in that mmap area.