One alternative you should consider when you need to call a function that is not threadsafe is using a global mutual exclusion (mutex) to run these functions.
You might try locking a mutex that you call the FOO MUTEX whenever you call foo(). This works if you know everything about the internal parts of foo(), but for general operating system functions, this is still not a completely safe solution.
For example, API foo() is not threadsafe because it calls API bar(), which calls threadUnsafeFoo(). The threadUnsafeFoo() function uses certain storage and control blocks in an unsafe way. In this example, those storage and control blocks are called DATA1.
Because the API threadUnsafeFoo() is unsafe, any application or system service that uses foo() is not threadsafe because using it with multiple threads might result in damaged data or undefined results.
Your application also uses a threadsafe API or system service that is called wiffle(). Wiffle() calls waffle(), which accesses storage and control blocks DATA1 in a threadsafe manner. (That is waffle() and a group of other APIs use an internal DATA1 MUTEX).
Your application does not know the underlying DATA1 connection between foo() and wiffle() system services. Your application therefore does not lock the FOO MUTEX before calling wiffle(). When a call to foo() (holding the FOO MUTEX) occurs at the same time as a call to wiffle(), the application's use of the not threadsafe function foo() causes DATA1 and whatever it represents to be damaged.
As complex as this scenario is, consider how complex resolving failures is when you do not know the internal details of all the functions involved.
Do not try to make operating system services or functions threadsafe by using your own serialization.