05-系统调用

进程管理(创建进程)

在Linux中,首先要创建一个进程。创建进程的系统调用叫fork。创建一个新的进程调要用fork来实现,其中老的进程叫 父进程,新的进程叫 子进程

当父进程调用fork创建进程的时候,子进程将父进程的数据全部拷贝了一份。当然,如果不进行特殊处理,父进程和子进程都按相同的程序代码进行下去,这样就没有意义了。

所以,我们往往会这样处理,对于fork系统调用的返回值,如果当前进程是子进程就返回0;如果当前进程是父进程就返回子进程的进程号。这样就首先有了一个区分。接下来我们就来判断,如果是父进程,我们就做原来的事情,而如果是子进程,我们需要请求另一个系统调用execve来执行另一个程序。

有时候,父进程要关心子进程的运行情况。有个系统调用waitpid,父进程可以调用它,将子进程的进程号作为参数传给它,父进程就可以知道子进程有没有运行完了。

内存管理

在操作系统中,每个进程都有自己的内存,互相之间不干扰,有独立的 进程内存空间。内存空间中,放程序代码的这部分,我们称为 代码段。放进程运行中产生数据的这部分,我们称为 数据段。其中局部变量的部分,在当前函数执行的时候起作用。当进入另一个函数时,这个变量就释放了;也有动态分配的,会较长时间保存,指明才会被销毁,这部分被称为

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

文件管理

对于文件的操作,下面有六个系统调用是最重要的:

  • 对于已有的文件,可以使用open打开这个文件,close关闭这个文件
  • 对于没有的文件,可以使用creat创建文件
  • 打开文件后,可以使用lseek跳到文件的某个位置
  • 可以对文件的内容进行读写,分别是readwrite

Linux有个特点,一切皆文件

  • 启动一个进程,需要一个程序文件,它是一个 二进制文件。
  • 启动的时候,需要加载一些配置文件,例如yml、properties等,这是文本文件;启动之后会打印一些日志,如果写到硬盘上,也是 文本文件。
  • 如果我们把日志打印到交互控制台上,在命令行打印出来的其实也是文件,是标准输出 stdout文件。
  • 这个进程的输出可以作为另一个进程的输入,这种方式称为 管道,管道也是文件。
  • 进程可以通过网络和其他进程进行通信,建立的 Socket,也是一个文件。
  • 进程需要访问外部设备,设备也是一个文件。
  • 文件被存储在文件夹里面,所以 文件夹也是一个文件。
  • 进程运行起来,想要看到进程运行的情况,会在/proc下面有对应的进程号,还是一系列文件。

每个文件,Linux都会分配一个 文件描述符,这是一个整数。有了这个文件描述符,我们就可以使用系统调用,查看或者干预进程运行的方方面面。

信号处理

当程序遇到异常情况时,例如中断,这是就需要向系统发送一个 信号(Signal)。经常遇到的信号有以下几种:

  • 执行一个程序的时候,在键盘里输入“Ctrl + C”
  • 非法访问内存
  • 硬件故障
  • 用户进程通过kill函数,将一个用户信号发送给另一个进程。

对于不严重的信息,可以忽略。可是对于类似SIGKILL(终止某个进程的信号)和SIGSTOP(中止某个进程的信号)是不能忽略的,可以执行对该信号默认的动作。每种信号都定义了默认的动作,例如硬件故障,默认终止;也可以提供信号处理函数,可以通过sigaction系统调用,注册一个信号处理函数。

进程间通信

当某个程序比较大的时候,可能有多个进程,不同的进程需要相互交流,配合才能完成,这就需要一个进程之间的沟通机制。进程之间的沟通方式有很多种,我们一个一个看。

首先就是发个消息,不需要一段很长的数据,这种方式称为 消息队列。由于一个程序的多个进程进行信息交互时,这个消息队列是在内核里的,我们可以通过msgget创建一个新的队列,msgsnd将消息发送到消息队列,而消息接收方可以使用msgrcv从队列种取消信息。

当两个进程需要交互的信息比较大的时候,可以使用 共享内存的方式。这时候,我们可以通过shmget创建一个共享内存块,通过shmat将共享内存映射到自己的内存空间,然后就可以读写了。

但是,两个进程访问共享内存块中的数据就会存在“竞争”的关系。这就需要有一种方式,让不同的进程能够排他的访问,这就是信号量的机制 Semaphore.

关于这个机制,我们说一种简单的场景。

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

网络沟通

如果一台Linux要和另一台Linux交流,这时候,我们就需要用到网络服务了。

不同的机器通过网络互相通信,它们需要遵循相同网络协议,TCP/IP网络协议栈。Linux内核里有对于网络协议栈的实现。那如何暴露出服务给进程使用呢?

网络服务是通过套接字Socket来提供服务的。Socket的中文意思可以译作“插口”,我们就可以想象,使用这个Socket将两个系统建立联系。

我们可以通过Socket系统调用建立一个Socket。Socket也是一个文件,也有一个文件描述符,也可以通过读写函数进行通信。

查看源代码中的系统调用

对于64位的操作系统,在源码中找到unistd_64.h文件,里面就有对于系统调用的定义。

Glibc

Glibc是Linux下使用的开源标准C库,它是GNU发布的libc库。Glibc为程序员提供丰富的API,除了例如字符串处理、数学运算等用户态服务之外,最重要的是封装了操作系统提供的系统服务,即系统调用的封装。

每个特定的系统调用对应了至少一个Glibc封装的库函数,比如系统提供的打开文件系统调用sys_open对应的是Glibc中的open函数。

有时候,Glibc一个单独的API可能调用多个系统调用,比如说,Glibc提供的printf函数就会调用如sys_opensys_mmapsys_writesys_close等等系统调用。

也有时候,多个API会对应同一个系统调用,如Glibc下实现的malloccallocfree等函数来分配和释放内存,都利用了内核的sys_brk的系统调用。

有问题?发送 issues 给我~

0%