当前位置: 首页 > news >正文

一:操作系统之系统调用

系统调用:用户程序与操作系统交互的桥梁

在计算机的世界里,应用程序是我们日常接触最多的部分,比如浏览器、文本编辑器、游戏等等。然而,这些应用程序并不能直接控制硬件资源,比如读写硬盘、创建新进程、发送网络数据包。它们需要一个“管家”来代劳,这个管家就是操作系统(Operating System)

那么,用户程序是如何向操作系统这个“管家”提出服务请求的呢?答案就是通过系统调用(System Call)

1. 系统调用:概念与必要性

概念: 系统调用是用户程序向操作系统请求服务的一种接口。可以理解为应用程序向操作系统发出的特殊函数调用请求。

为什么需要系统调用?

  1. 保护硬件资源: 操作系统运行在更高的权限级别(通常称为内核模式或特权模式),可以直接访问和管理所有硬件资源。用户程序运行在较低的权限级别(用户模式),受到操作系统的限制,不能随意访问硬件,这防止了恶意或错误的程序破坏系统。
  2. 提供抽象和便利: 操作系统将复杂的硬件操作封装成简单的、高级的服务。用户程序无需了解底层硬件细节(例如硬盘控制器如何工作),只需调用一个简单的系统调用(如 readwrite)即可完成文件读写。
  3. 系统安全与稳定: 通过系统调用,操作系统可以对用户程序的请求进行检查和验证(例如,检查是否有权限访问某个文件),防止非法操作,从而保证系统的安全和稳定运行。

简单来说,系统调用就像是用户程序进入操作系统内核的唯一合法通道。用户程序在用户模式下运行,当需要操作系统提供的服务时,就通过系统调用“陷入”到内核模式,由操作系统内核处理请求,完成后再返回用户模式。

2. 系统调用的实现机制:陷入 (Trap) / 中断 (Interrupt)

用户程序并不能直接调用内核代码中的函数,因为它没有足够的权限。当用户程序执行到一条请求系统服务的指令时,会触发一个特殊的事件,这个事件被称为陷入 (Trap)软件中断 (Software Interrupt)

实现流程:

  1. 参数准备: 用户程序在发起系统调用前,会将所需的参数(比如要打开的文件名、要写入的数据、文件描述符等)放置在特定的寄存器中或者压入栈中。
  2. 系统调用指令: 用户程序执行一条特殊的机器指令(例如,x86 架构上的 syscallint 0x80),这条指令就是陷入指令。
  3. 模式切换: CPU 检测到这条陷入指令后,会立即执行以下动作:
    • 硬件自动切换CPU的运行模式从用户模式切换到内核模式(提升权限)。
    • 硬件保存当前用户程序的上下文信息,包括寄存器的值、程序计数器 (PC) 等,以便系统调用完成后能够正确返回。
    • 硬件跳转到一个预设的内核入口点。这个入口点的地址通常是固定的,存储在一个称为中断向量表或系统调用向量表的结构中。
  4. 内核处理:
    • 内核入口点代码会检查是哪种系统调用(通常通过检查某个寄存器中的系统调用号来确定)。
    • 根据系统调用号,内核找到对应的系统调用处理函数。
    • 内核会验证用户程序传递的参数是否合法(例如,指针是否有效、权限是否足够)。
    • 内核执行请求的服务(例如,查找文件、分配内存、调度进程等)。
  5. 结果返回:
    • 服务完成后,内核将结果(例如,文件描述符、成功/失败状态码等)放置在用户程序可以访问的地方(通常是寄存器)。
    • 内核恢复之前保存的用户程序上下文信息。
    • 内核切换CPU的运行模式从内核模式切换回用户模式(降低权限)。
    • 内核跳转回用户程序,继续执行系统调用指令的下一条指令。

整个过程看起来像是用户程序主动“跳入”内核,请求服务,然后内核处理完再“跳回”用户程序。这个过程是原子性的,保证了系统调用的完整执行。

3. 常见系统调用分类与实例

为了方便管理和理解,操作系统通常会将系统调用按功能进行分类。以下是一些常见的分类及其详细例子:

3.1 进程控制 (Process Control)

这类系统调用用于创建、终止、加载、等待进程,以及获取进程信息。

  • fork() / clone() (Unix/Linux)
    • 功能: 创建一个新进程,它是当前进程的副本(子进程)。子进程继承父进程的大部分资源。
    • 例子: 当你在命令行中输入 ls 并回车时,Shell 程序(本身也是一个进程)不会直接执行 ls 的代码。它会调用 fork() 创建一个子进程,然后在子进程中调用 exec() 类系统调用加载并运行 ls 程序。
    • C语言伪代码:
      pid_t pid = fork();
      if (pid == 0) { // 子进程// 在这里调用 exec() 执行其他程序execlp("ls", "ls", "-l", NULL);// 如果execlp失败,子进程会继续执行下面的代码,通常会调用 exit() 退出perror("execl error");exit(EXIT_FAILURE);
      } else if (pid > 0) { // 父进程// 父进程通常会调用 wait() 等待子进程结束waitpid(pid, NULL, 0);printf("Child process finished.\n");
      } else { // fork 失败perror("fork error");
      }
      
  • exec() 系列 (Unix/Linux) / CreateProcess() (Windows)
    • 功能: 用一个新的程序替换当前进程的映像。进程ID不会改变,但代码、数据和堆栈会被新程序的内容覆盖。
    • 例子: 接上面的 fork() 例子,子进程在创建后会立即调用 exec() 类系统调用加载并运行 ls 程序。
    • C语言伪代码: (在 fork() 后的子进程中使用)
      // 当前进程会被 /bin/ls 替换
      execl("/bin/ls", "ls", "-l", "/home", NULL);
      // 如果走到这里,说明 exec 失败了
      perror("exec failed");
      exit(EXIT_FAILURE);
      
  • exit() / _exit() (Unix/Linux) / ExitProcess() (Windows)
    • 功能: 终止当前进程的执行。可以带一个状态码,表示进程是正常结束还是异常结束。
    • 例子: 当一个程序完成其任务后,它会调用 exit() 来退出,释放资源并将控制权交还给操作系统(或父进程)。
    • C语言伪代码:
      // 程序完成,正常退出
      exit(EXIT_SUCCESS);
      // 或者发生错误,异常退出
      exit(EXIT_FAILURE);
      
  • wait() / waitpid() (Unix/Linux)
    • 功能: 父进程挂起执行,直到其某个子进程终止。可以获取子进程的终止状态。
    • 例子: Shell 在执行一个命令后,会 wait 其子进程(执行该命令的进程)完成,然后才会显示下一个命令提示符。
    • C语言伪代码:
      // 等待任意一个子进程结束
      wait(NULL);
      // 等待特定PID的子进程结束
      waitpid(child_pid, &status, 0);
      
  • brk() / sbrk() (Unix/Linux)
    • 功能: 用于调整进程数据段的大小,通常是增加堆内存的分配。
    • 例子: C语言标准库中的 malloc() 函数在底层需要更多堆内存时,可能会调用 brk()sbrk() 系统调用向操作系统请求。

3.2 文件管理 (File Management)

这类系统调用用于文件的创建、删除、打开、关闭、读写、定位等操作。

  • open() (Unix/Linux) / CreateFile() (Windows)
    • 功能: 打开一个文件,并返回一个文件描述符(一个整数,代表这个打开的文件)。
    • 例子: 用户程序需要读取或写入某个文件时,首先需要调用 open()
    • C语言伪代码:
      int fd = open("mydata.txt", O_RDWR | O_CREAT, 0666); // 打开或创建文件,可读写,权限0666
      if (fd == -1) {perror("open error");
      }
      
  • read() (Unix/Linux) / ReadFile() (Windows)
    • 功能: 从一个文件描述符中读取指定数量的数据到缓冲区。
    • 例子: 读取配置文件内容,或者从标准输入读取用户输入。
    • C语言伪代码:
      char buffer[100];
      ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1); // 从fd读取最多99字节到buffer
      if (bytes_read > 0) {buffer[bytes_read] = '\0'; // 确保字符串以null结尾printf("Read: %s\n", buffer);
      } else if (bytes_read == -1) {perror("read error");
      }
      
  • write() (Unix/Linux) / WriteFile() (Windows)
    • 功能: 将缓冲区中的数据写入到一个文件描述符。
    • 例子: 将数据写入日志文件,或者向标准输出打印信息(printf 函数底层会调用 write)。
    • C语言伪代码:
      const char *data = "Hello, System Calls!\n";
      write(fd, data, strlen(data)); // 将data写入到fd
      
  • close() (Unix/Linux) / CloseHandle() (Windows)
    • 功能: 关闭一个文件描述符,释放与之相关的资源。
    • 例子: 当一个程序不再需要访问某个文件时,应该调用 close() 来释放该文件描述符,避免资源泄露。
    • C语言伪代码:
      close(fd); // 关闭文件
      
  • lseek() (Unix/Linux) / SetFilePointer() (Windows)
    • 功能: 改变文件描述符当前的读写位置。
    • 例子: 在文件中跳过头部数据,直接从某个偏移量开始读取;或者实现文件的随机读写。
    • C语言伪代码:
      lseek(fd, 100, SEEK_SET); // 将文件指针移动到文件开头后的第100个字节
      
  • unlink() (Unix/Linux) / DeleteFile() (Windows)
    • 功能: 删除一个文件。
    • 例子: 程序创建了一个临时文件用于处理数据,完成后需要将其删除。
    • C语言伪代码:
      unlink("tempfile.txt"); // 删除文件
      

3.3 设备管理 (Device Management)

这类系统调用用于请求/释放设备、读写设备等。在许多现代操作系统(尤其是类Unix系统)中,设备也被抽象成文件,可以通过文件管理相关的系统调用(如 open, read, write, close)来访问。

  • ioctl() (Input/Output Control) (Unix/Linux)
    • 功能: 一个通用的设备控制接口,用于执行设备特定的输入/输出操作,这些操作不适合标准的 read/write 模型。
    • 例子: 控制终端属性(如设置波特率)、控制磁带机、弹出光驱、配置网络接口等。
    • C语言伪代码:
      int fd = open("/dev/tty", O_RDWR); // 打开终端设备
      struct termios term;
      ioctl(fd, TCGETS, &term); // 获取终端属性
      // 修改属性...
      ioctl(fd, TCSETS, &term); // 设置终端属性
      close(fd);
      
  • read() / write() 用于设备:
    • 功能: 通过文件描述符与设备进行数据交换。
    • 例子: 从键盘设备 /dev/tty 读取输入,向显示设备 /dev/fb0 写入图像数据,通过网络套接字发送/接收数据。

3.4 信息维护 (Information Maintenance)

这类系统调用用于获取或设置系统信息、进程信息、文件状态等。

  • getpid() / getppid() (Unix/Linux)
    • 功能: 获取当前进程的进程ID (PID) 或其父进程的PID (PPID)。
    • 例子: 日志记录时标记是哪个进程产生的日志;父进程通过子进程的PID进行管理。
    • C语言伪代码:
      pid_t my_pid = getpid();
      pid_t parent_pid = getppid();
      printf("My PID: %d, Parent PID: %d\n", my_pid, parent_pid);
      
  • getuid() / geteuid() (Unix/Linux)
    • 功能: 获取当前进程的真实用户ID (UID) 或有效用户ID (EUID)。用于权限检查。
    • 例子: 程序判断当前用户是否有执行某个操作的权限。
  • time() / gettimeofday() (Unix/Linux)
    • 功能: 获取当前的系统时间或精确到微秒的时间。
    • 例子: 程序需要记录事件发生的时间戳,或者计算代码执行的时间。
    • C语言伪代码:
      time_t current_time = time(NULL); // 获取当前时间戳
      printf("Current time: %ld\n", current_time);struct timeval tv;
      gettimeofday(&tv, NULL); // 获取微秒级别时间
      printf("Microsecond time: %ld.%06ld\n", tv.tv_sec, tv.tv_usec);
      
  • stat() / fstat() (Unix/Linux) / GetFileAttributes() (Windows)
    • 功能: 获取文件或文件描述符的状态信息,包括文件大小、权限、所有者、创建/修改时间等。
    • 例子: 文件浏览器程序需要显示文件的详细信息时会调用 stat
    • C语言伪代码:
      struct stat file_stat;
      if (stat("mydata.txt", &file_stat) == 0) {printf("File size: %lld bytes\n", (long long)file_stat.st_size);printf("Permissions: %o\n", file_stat.st_mode & 0777); // 打印文件权限
      } else {perror("stat error");
      }
      
  • uname() (Unix/Linux)
    • 功能: 获取系统信息,如操作系统名称、版本、硬件架构等。
    • 例子: 程序需要检查运行环境的兼容性时使用。

3.5 通信 (Communication)

这类系统调用用于实现进程间通信 (IPC) 和网络通信。

  • pipe() (Unix/Linux)
    • 功能: 创建一个无名管道,用于父子进程或兄弟进程之间的单向通信。
    • 例子: Shell 命令 ls | grep keyword 中,ls 命令的输出通过管道传递给 grep 命令作为输入。
    • C语言伪代码:
      int pipefd[2]; // pipefd[0] for read, pipefd[1] for write
      if (pipe(pipefd) == -1) {perror("pipe error");
      }
      // 在 fork() 创建子进程后,父子进程分别关闭不需要的端点,然后通过管道进行读写
      
  • socket() (Unix/Linux) / socket() (Windows Sockets)
    • 功能: 创建一个网络套接字,它是网络通信的端点。
    • 例子: 任何网络应用程序(浏览器、服务器、聊天软件)在进行网络通信前都需要创建套接字。
http://www.xdnf.cn/news/521605.html

相关文章:

  • 【应用开发十】pwm
  • numpy数组的拆分和组合
  • 【Linux服务器】-虚拟机安装(CentOS7.9)
  • 我的世界模组开发——方块(2)
  • 图像定制大一统?字节提出DreamO,支持人物生成、 ID保持、虚拟试穿、风格迁移等多项任务,有效解决多泛化性冲突。
  • 串口通讯协议学习
  • BiRefNet V3版 - 一个高精度的高分辨率图像抠图模型,AI“抠图之王” 支持50系显卡 本地一键整合包下载
  • 【言语理解】逻辑填空之逻辑对应11
  • 【MySQL】存储过程,存储函数,触发器
  • fcQCA模糊集定性比较分析法-学习笔记
  • OpenHarmony 5.0状态栏息屏状态下充电然后亮屏会出现电量跳变情况
  • 19-I2C库函数
  • ProfibusDP转ModbusRTU的实用攻略
  • WindowsPE文件格式入门11.资源表
  • 算法-js-柱状图中最大的矩形
  • 计算机系统的层次结构
  • 采摘桑葚
  • 中级网络工程师知识点6
  • 掌握生成式 AI 的未来:Google Cloud 全新认证
  • Office 中 VBE 的共同特点与区别
  • 【typenum】 12 类型级数组(array.rs)
  • Node.js 框架
  • 20-HAL库
  • 进程控制总结
  • Spyglass:参数(parameter)及其设置方式
  • 考研数学积分学
  • supervisorctl守护进程
  • PCB设计实践(十九)PCB设计中NPN/PNP选型策略
  • C++(23):容器类<vector>
  • C++控制结构详解:if-else、switch、循环(for/while/do-while)