https://time.geekbang.org/column/article/89251
系统调用 - 图1

进程管理

创建进程的系统调用叫fork

在Linux中,要创建进程,需要一个老的进程调用fork来实现。其中,老的进程叫作父进程(Parent Process),新的进程叫作子进程(Child Process)

等待子进程结束的系统调用叫waitpid,父进程可以调用它,将子进程的进程号作为参数传给它,这样父进程就知道子进程运行完了没有,成功与否。

创建进程的总结:

1、Linux中父进程调用fork创建子进程。
2、父进程调用fork时,子进程拷贝所有父进程的数据接口和代码过来。
3、当前进程是子进程,fork返回0;当前进程是父进程,fork返回子进程进程号
4、如果返回0,说明当前进程是子进程,子进程请求execve系统调用,执行另一个程序。
5、如果返回子进程号,说明当前进程是父进程,按照原父进程原计划执行。
6、父进程要对子进程负责,调用waitpid将子进程进程号作为参数,父进程就能知道子进程运行完了没有,成功与否。
7、操作系统启动的时候先创建了一个所有用户进程的“祖宗进程”

内存管理

代码段:放代码的空间
数据段:执行过程中产生的数据的空间

堆里面分配内存的系统调用:brkmmap
brk:分配的内存数量比较小的时候使用,会和原来的堆的数据连在一起
mmap:分配的内存数量比较大的时候使用,重新划分一块区域。

文件管理

对于文件操作,常见的六个系统调用:

  • open:打开文件
  • close:关闭文件
  • creat:创建文件
  • lseek:跳到文件的某个位置
  • read:读文件
  • write:写文件

文件描述符:对于每个文件,Linux都会分配一个文件描述符(File Descriptor),本质上是一个整数。Linux一切皆文件,有了文件描述符就可以使用系统调用,查看或者干预进程运行的方方面面。

信号处理

经常遇到的信号有以下几种:

  • 在执行一个程序的时候,在键盘输入CTRL + C,这就是中断的信号,正在执行的命令就会中止退出;
  • 非法访问内存
  • 硬件故障
  • 用户进程通过kill函数,将一个用户信号发送给另一个进程。

每种信号都定义了默认的动作,例如硬件故障,默认终止;
也可以提供信号处理函数,可以通过sigaction系统调用,注册一个信号处理函数。

进程间通信

进程间通信的方式有很多种。

首先就是发个消息,不需要一段很长的数据,这种方式称为消息队列(Message Queue)。这个消息队列是在内核里的,可以通过msgget创建一个新的队列,msgsnd将消息发送到消息队列,而消息接受方可以使用msgrcv从队列中取消息。

当需要交互的信息比较大时,可以使用共享内存的方式。通过shmget创建一个共享内存块,通过shmat将共享内存映射到自己的内存空间,然后就可以读写了。这个时候,会带来另外一个问题——资源竞争。比如说大家同时修改同一块数据怎么办?需要有一种方式,让不同的人能够排他地访问,这就是信号量的机制Semaphore

一个简单的场景:对于只允许一个人访问的需求,我们可以将信号量设为 1。当一个人要访问的时候,先调用sem_wait。如果这时候没有人访问,则占用这个信号量,他就可以开始访问了。如果这个时候另一个人要访问,也会调用 sem_wait。由于前一个人已经在访问了,所以后面这个人就必须等待上一个人访问完之后才能访问。当上一个人访问完毕后,会调用sem_post将信号量释放,于是下一个人等待结束,可以访问这个资源了。

网络通信

不同机器要想通过网络相互通信,要遵循相同的网络协议,即TCP/IP网络协议栈
网络协议栈以服务的形式提供给上层应用使用。

网络服务是通过套接字Socket来提供服务的。

平常开发时,并没有直接使用系统调用,而是使用中介Glibc
Glibc是Linux下使用的开源的标准C库,它是GNU发布的libc库。Glibc为程序员提供了丰富的API,最重要的是封装了操作系统提供的系统服务,即系统调用的封装。比如说,系统提供打开文件系统调用sys_open对应的是Glibc中的open函数。