So I understand what system calls are for and what they do. Pretty much anything that requires a computer's resources invokes a system call at some point. My question is, how does a user-created program in Python for example compile and know where to make system calls? If I write a "Hello World" program, does the compiler (interpreter in this case) know to associate the print() function in Python with a particular system call in its host operating system? Where in the process of writing a user program in a programming language and turning it into 1s and 0s do system calls come into play? Thanks.
how does a user-created program in Python for example compile and know where to make system calls?
You can explore some of this yourself.
Given this Python program: print("Hello")
, on a UNIX system one can guess that a write(2)
system call will eventually be invoked. Let's see if that's true:
gdb -q --args python -c 'print("Hello")'
(gdb) catch syscall write
Catchpoint 1 (syscall 'write' [1])
(gdb) run
Starting program: /usr/bin/python -c print\(\"Hello\"\)
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Catchpoint 1 (call to syscall write), 0x00007ffff6f3cdd4 in __GI___libc_write (fd=1, buf=0x555555b2f9f0, nbytes=6) at ../sysdeps/unix/sysv/linux/write.c:26
26 ../sysdeps/unix/sysv/linux/write.c: No such file or directory.
(gdb) x/s $rsi
0x555555b2f9f0: "Hello\n"
(gdb) bt
#0 0x00007ffff6f3cdd4 in __GI___libc_write (fd=1, buf=0x555555b2f9f0, nbytes=6) at ../sysdeps/unix/sysv/linux/write.c:26
#1 0x00007ffff6ecdb9d in _IO_new_file_write (f=0x7ffff720d760 <_IO_2_1_stdout_>, data=0x555555b2f9f0, n=6) at fileops.c:1183
#2 0x00007ffff6eccf3f in new_do_write (fp=0x7ffff720d760 <_IO_2_1_stdout_>, data=0x555555b2f9f0 "Hello\n", to_do=to_do@entry=6) at libioP.h:839
#3 0x00007ffff6ecece9 in _IO_new_do_write (fp=<optimized out>, data=<optimized out>, to_do=6) at fileops.c:432
#4 0x00007ffff6ece26f in _IO_new_file_xsputn (f=0x7ffff720d760 <_IO_2_1_stdout_>, data=<optimized out>, n=1) at libioP.h:839
#5 0x00007ffff6ec2c3f in __GI__IO_fputs (str=0x55555580d74d "\n", fp=0x7ffff720d760 <_IO_2_1_stdout_>) at libioP.h:839
#6 0x00005555556966ac in PyFile_WriteString ()
#7 0x000055555564c42d in PyEval_EvalFrameEx ()
#8 0x000055555564610a in PyEval_EvalCodeEx ()
#9 0x0000555555645a29 in PyEval_EvalCode ()
#10 0x0000555555676c5f in ?? ()
#11 0x00005555556a4846 in PyRun_StringFlags ()
#12 0x00005555556a567c in PyRun_SimpleStringFlags ()
#13 0x000055555562005a in Py_Main ()
#14 0x00007ffff6e7652b in __libc_start_main (main=0x55555561fb30 <main>, argc=3, argv=0x7fffffffdc78, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>,
stack_end=0x7fffffffdc68) at ../csu/libc-start.c:308
#15 0x000055555561fa4a in _start ()
What can you deduce from this?
write
system call is indeed invoked, with expected characters in the $RSI
register (which happens to be where the second argument to system calls must be for this particular platform).__GI___libc_write
routine, which is a system call wrapper provided by libc
.__libc_write
directly. It called __GI__IO_fputs
(which is actually an alias for fputs
.print
into a call to PyFile_WriteString
, and that function delegated the actual work to libc.This is not at all unusual -- in fact most user-level programs do not execute system calls directly. Instead, they call libc
routines, such as write
or fputs
, and let the libc worry how to implement these routines in terms of system calls.
By doing so, they can abstract the details of the particular machine and OS; they expect to find some libc
on any machine, and only libc
developers have to worry about actual system call details.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With