Linux内核:系统调用大全(持续更新中)

本文详细介绍了Linux系统调用中的sys_brk函数,用于改变进程堆大小,以及dup2函数,用于复制文件描述符。还提供了示例代码和运行结果,展示了如何使用这些功能进行内存管理和管道通信。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. sys_brk

函数原型

/*
 * sys_brk()是一个操作系统调用,用于更改进程的堆空间大小。
 * sys_brk()函数接收一个无符号长整型参数brk,表示要求的新的程序数据段(堆)结束地址。
 * 如果brk的值大于当前的程序数据段结束地址,则增加堆的大小;如果brk的值小于当前的程序数据段结束地址,则缩小堆的大小。
 * 函数返回值为long类型,表示数据段结束地址。如果分配失败,则返回-1。
 */
asmlinkage long sys_brk(unsigned long brk);

示例代码

// /*================================================================
// *   Copyright (C) 2023 baichao All rights reserved.
// *
// *   文件名称:sys_brk_demo.c
// *   创 建 者:baichao
// *   创建日期:2023年08月01日
// *   描    述:
// *
// ================================================================*/

#include <unistd.h>
#include <stdio.h>
#include <sys/syscall.h> // 增加此头文件

int main()
{
    void *cur_brk;

    // 获取当前程序数据段结束地址
    long ret = syscall(__NR_brk, 0);
    cur_brk = (void *)ret;
    printf("start, curr data sec end pos: %p\n", cur_brk);

    // 增加堆的大小
    long delta = 1024;
    ret = syscall(__NR_brk, cur_brk + delta); // 堆增长delta
    cur_brk = (void *)ret;
    printf("add, curr data sec end pos: %p\n", cur_brk);

    // 减小堆的大小
    delta = -512;
    ret = syscall(__NR_brk, cur_brk + delta); // 堆缩小delta
    cur_brk = (void *)ret;
    printf("sub, curr data sec end pos: %p\n", cur_brk);

    // 获取当前程序数据段结束地址
    ret = syscall(__NR_brk, 0);
    cur_brk = (void *)ret;
    printf("end, curr data sec end pos: %p\n", cur_brk);

    return 0;
}

执行结果如下:
在这里插入图片描述
需要注意的是,在使用sys_brk()函数时必须特别小心,因为它可以轻松地破坏程序的内存布局。通常情况下,建议使用malloc()等库函数来进行内存分配,以避免出现问题。

linux c有一个对于sys_brk的系统调用的封装函数sbrk:

// __delta如果为正,当前程序数据段结束地址在现有的地址上增加delta,即堆增长|delta|
// __delta如果为负,当前程序数据段结束地址在现有的地址上减少delta;即堆减小|delta|
// 返回值:返回增加前的堆的尾地址。
void *sbrk (intptr_t __delta) __THROW;

sbrk 函数用于扩展或缩小进程的堆大小,即为程序动态分配内存提供支持。它可以接受一个整数参数 increment,表示需要增加或减少的字节数。如果 increment 为正数,则将堆大小增加相应的字节数;如果为负数,则将堆大小减小相应的字节数。
示例代码如下:

/*================================================================
*   Copyright (C) 2023 baichao All rights reserved.
*   
*   文件名称:sbrk.c
*   创 建 者:baichao
*   创建日期:2023年08月01日
*   描    述:
*
================================================================*/


#include <unistd.h>
#include <stdio.h>

int main() {
    void *cur_brk;

    // 获取当前程序数据段结束地址
    cur_brk = sbrk(0);
    printf("start, curr data sec end pos: %p\n", cur_brk);

    // 增加堆的大小
    long delta = 100*1024*1024;
    cur_brk = sbrk(delta); // 堆增长delta, 此时的返回值cur_brk是增加前的地址
    printf("add1, curr data sec end pos: %p\n", cur_brk);
    cur_brk = sbrk(0);
    printf("add2, curr data sec end pos: %p\n", cur_brk);

    // 减小堆的大小
    delta = 50*1024*1024;
    cur_brk = sbrk(delta); // 堆缩小delta,同理,此时的返回值cur_brk是减小前的地址
    printf("sub1, curr data sec end pos: %p\n", cur_brk);
    cur_brk = sbrk(0);
    printf("sub2, curr data sec end pos: %p\n", cur_brk);

    return 0;
}

运行结果如下:
在这里插入图片描述

2. dup2

函数原型

int dup2(int oldfd, int newfd);

dup2系统调用创建一个新的文件描述符,这个新的文件描述符与指定的旧文件描述符指向同一个文件。这意味着新的文件描述符可以被用来进行读写操作,而这些操作会影响到与旧文件描述符相同的文件状态和文件偏移量。

其中,oldfd是已经打开的文件描述符,newfd是新文件描述符的起始值。如果newfd已经打开,则它会被关闭并重新打开,以便可以重新使用这个文件描述符号。

当dup2成功执行时,它会返回新的文件描述符,这个文件描述符与oldfd共享相同的文件状态和文件偏移量。如果oldfd不是一个有效的文件描述符,dup2会失败,并且newfd不会被关闭。如果oldfd和newfd的值相同,那么dup2实际上不会执行任何操作,只是简单地返回newfd。

示例程序

/*================================================================
*   Copyright (C) 2024 baichao All rights reserved.
*
*   文件名称:pipe_dup2_std.c
*   创 建 者:baichao
*   创建日期:2024年04月06日
*   描    述:模拟shell管道通信。核心原理就是利用dup2系统调用复制文件描述符。
             简单来说,就是第一个子进程的标准输出被绑定到了管道的写入端,于是第一个命令的输出,写入了管道。
             而第二个子进程管道将其标准输入绑定到管道的读取端,只要管道里面有了内容,这些内容就成了标准输入。
*
================================================================*/

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

#define READ_END 0
#define WRITE_END 1

int main() {
  int pipefd[2];
  pid_t pid1, pid2;

  // 创建管道
  if (pipe(pipefd) < 0) {
    perror("pipe");
    exit(EXIT_FAILURE);
  }

  // 创建第一个子进程
  pid1 = fork();
  if (pid1 < 0) {
    perror("fork");
    exit(EXIT_FAILURE);
  } else if (pid1 == 0) {  // 子进程1
    // 关闭管道的读端
    close(pipefd[READ_END]);

    // 将标准输出重定向到管道的写端
    dup2(pipefd[WRITE_END], STDOUT_FILENO);

    // 关闭管道的旧的写端
    close(pipefd[WRITE_END]);

    // 子进程1写入数据到标准输出(现在关联到管道的写端)
    printf("子进程1写入的数据\n");
    exit(EXIT_SUCCESS);
  }

  // 创建第二个子进程
  pid2 = fork();
  if (pid2 < 0) {
    perror("fork");
    exit(EXIT_FAILURE);
  } else if (pid2 == 0) {  // 子进程2
    // 关闭管道的写端
    close(pipefd[WRITE_END]);

    // 将标准输入重定向到管道的读端
    dup2(pipefd[READ_END], STDIN_FILENO);

    // 关闭管道的旧的读端
    close(pipefd[READ_END]);

    // 子进程2从标准输入(现在关联到管道的读端)读取数据
    for (int i = 0; i < 10; i++) {
      char buffer[1024];
      ssize_t bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer) - 1);
      if (bytes_read > 0) {
        buffer[bytes_read] = '\0';  // 确保字符串以null字符结尾
        printf("子进程2读取到的数据: %s\n", buffer);
        break;
      }
      printf("bytes_read:%d,error:%s\n\n", bytes_read, strerror(errno));
      sleep(1);
    }

    exit(EXIT_SUCCESS);
  }

  // 父进程关闭管道两端
  close(pipefd[READ_END]);
  close(pipefd[WRITE_END]);

  // 等待子进程结束
  wait(NULL);
  wait(NULL);

  return 0;
}

运行结果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值