Linux系统编程

简介: ## 标准I/O> input&output 它是一切实现的基础stdio 标准 IO sysio 系统ui## 文件IO:  标准IO:优点是可移植性高,缺点是性能比系统 IO 差,且功能没有系统 IO 丰富。  系统IO:因为是内核直接提供的系统调用函数,所以性能比标准 IO 高,但是可移植性比标准 IO 差如果一个问题的解决需要用到`IO`,而且这个时候标准`IO`和系统`IO`都能解决问题的时候,我们优先使用标准`IO`;原因 : 移植性好、合并系统调用## 虚拟地址空间内核区:内核区是受保护的,用户不能对其进行读写操作,否则会引发段错误。

Linux系统编程

标准I/O

input&output 它是一切实现的基础

stdio 标准 IO sysio 系统ui

文件IO:

  标准IO:优点是可移植性高,缺点是性能比系统 IO 差,且功能没有系统 IO 丰富。

  系统IO:因为是内核直接提供的系统调用函数,所以性能比标准 IO 高,但是可移植性比标准 IO 差

如果一个问题的解决需要用到IO,而且这个时候标准IO和系统IO都能解决问题的时候,我们优先使用标准IO

原因 : 移植性好、合并系统调用

虚拟地址空间

内核区:内核区是受保护的,用户不能对其进行读写操作,否则会引发段错误。

用户区 :用户可以直接进行操作

进程和程序的区别:程序是我们正在运行中的代码 ,进程在运行中会加载到内存里。

MMU 虚拟内存管理 :虚拟内存中的数据都会被MMU映射到实际中的物理内存中

虚拟地址空间在不同机器上的大小 :32位机器的大小是 2^32 64位机器的大小是2^48

文件描述符

文件描述符.png

linux系统一切皆文件

静态库的制作

静态库.png

main.cpp

#include"test.h"
using namespace std;
const int N = 1010;
struct student{
  int age;
  string name;
  int sorce;
  string gender;

}a[N];
int n;
int main()
{
       cout<<add(1,2)<<endl;

}

test.cpp

#include"test.h"
int add(int x,int y){
      return x+y;
}

test.h

#include<iostream>
#include<algorithm>
int add(int x,int y);

第一步 : 生成 .o 文件

g++ -c main.cpp test.cpp
gcc -o main.c test.c

会生成 main.otest.o两个文件,并不是可执行文件,所以并不会链接,不需要使用-o参数。

第二步 生成 .a 并使用

ar -r lq.a main.o test.o
ar rcs lq.a main.o test.o

加载a库,即用gcc命令生成目标文件时指明静态库名,gcc将会从静态库中将公用函数连接到目标文件中。注意,gcc会在静态库名前加上前缀lib,然后追加扩展名.a得到的静态库文件名来查找静态库文件。

使用方式:

g++ -o main main.cpp main.o test.o lq.a

或者

g++ -o main main.cpp -L. -ltest

动态库的制作

  1. test.h 文件中定义所需的函数和类。确保包含了合适的头文件保护(#ifndef#define#endif)。

  2. test.cpp 编译为动态库。在命令行或终端中执行以下命令:

    shellCopy Codeg++ -shared -fPIC -o libtest.so test.cpp
    

    这将编译 test.cpp 并生成名为 libtest.so 的动态库文件。

  3. main.cpp 文件中使用 test.h 中定义的函数或类。确保在头部加入 #include "test.h"

  4. 编译 main.cpp 并链接到生成的动态库。在命令行或终端中执行以下命令:

    shellCopy Codeg++ -o main main.cpp -L. -ltest
    

    这将编译 main.cpp 并链接到动态库 libtest.so。选项 -L. 告诉编译器在当前目录搜索动态库,而 -ltest 指定链接到名为 libtest.so 的库。

  5. 运行生成的可执行文件。在命令行或终端中执行以下命令:

    shellCopy Code./main
    

这样就可以将 test.cpp 编译为动态库,并在 main.cpp 中成功调用它了

在不同文件夹下制作动态库

如果您的源文件(main.cpptest.cpptest.h)不在同一个目录下,您可以采取以下方法进行编译和链接。

假设 main.cpp 位于 /path/to/main.cpptest.cpp 位于 /path/to/test.cpptest.h 位于 /path/to/test.h

  1. 编译 test.cpp:首先,您需要编译 test.cpp 并生成对象文件 test.o。使用以下命令:
g++ -c /path/to/test.cpp -o test.o -fPIC

这将编译 test.cpp 并生成 test.o

生成 .o文件

  1. 编译 main.cpp:接下来,您需要编译 main.cpp 并生成对象文件 main.o。使用以下命令:
g++ -c /path/to/main.cpp -o main.o

这将编译 main.cpp 并生成 main.o

生成 .so文件

  1. 生成动态库:然后,您可以使用以下命令将 test.omain.o 链接为一个动态库。假设您想要生成的动态库名为 libtest.so,使用以下命令:
g++ -shared -o libtest.so test.o main.o

这将链接 test.omain.o 并生成 libtest.so 动态库文件。

编译链接

  1. 编译和链接可执行文件:最后,您可以使用以下命令来编译和链接可执行文件,以测试动态库的加载和调用:
g++ -o main main.o -ldl

这将编译 main.o 并链接动态库,并生成可执行文件 main

现在,如果一切顺利,您可以运行生成的可执行文件 ./main,它会自动加载动态库并调用其中的函数。

请注意,在上述命令中,使用了绝对路径来引用源文件。如果在命令中使用相对路径,请确保路径是正确的,并根据实际情况进行相应的更改。

动态库加载失败的原因

未指定动态库的绝对路径

什么是生成与位置无关的代码?

因为动态库只在程序运行的时候才加载进内存,并且在内存中的位置是不确定的,如果生成的是与位置有关的库,那么在一些其他位置就无法成功运行。因此为了在不同的内存地址能够正常运行程序,需要使用fpic

静态库的制作流程

静态库的制作.png

动态库的制作流程

动态库的制作.png

Makefile:开始大型项目的构建

一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中, Makefile 文件定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编 译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 Makefile 文件就 像一个 Shell 脚本一样,也可以执行操作系统的命令。

◼ Makefile 带来的好处就是“自动化编译” ,一旦写好,只需要一个 make 命令,整 个工程完全自动编译,极大的提高了软件开发的效率。make 是一个命令工具,是一个 解释 Makefile 文件中指令的命令工具,一般来说,大多数的 IDE 都有这个命令, 比如 Delphi 的 make,Visual C++ 的 nmake,Linux 下 GNU 的 make

命名规则

1.文件命名 makefile 或者 Makefile

  1. Makefile 规则  一个 Makefile 文件中可以有一个或者多个规则 目标 ...: 依赖 ... 命令(Shell 命令) ...

    3.目标:最终要生成的文件(伪目标除外)

    4.依赖:生成目标所需要的文件或是目标

    5.命令:通过执行命令对依赖操作生成目标(命令前必须 Tab 缩进)  Makefile 中的其它规则一般都是为第一条规则服务的

工作原理

命令在执行之前,需要先检查规则中的依赖是否存在

如果存在,执行命令,如果不存在,向下检查其它的规则,检查有没有一个规则是用来生成这个依赖的, 如果找到了,则执行该规则中的命令

检测更新,在执行规则中的命令时,会比较目标和依赖文件的时间  如果依赖的时间比目标的时间晚,需要重新生成目标  如果依赖的时间比目标的时间早,目标不需要更新,对应规则中的命令不需要被 执行

变量

Makefile变量.png

模式匹配

模式匹配.png

总结 : makefile的其他规则都是为第一条规则服务的

案例

main.cpp

#include "test.h"

int main() {

   cout<<add(2,3)<<endl;
   cout<<endl;
}

test.h

#include"test.h"
int add(int x,int y){
     return x>y?x:y;
}

test.cpp

#include"test.h"
int add(int x,int y){
     return x>y?x:y;
}

执行Makefile命令

app:test.cpp
    g++ test.cpp main.cpp -o app

输出

3

gdb调试

什么是gdb?

GDB 是由 GNU 软件系统社区提供的调试工具,同 GCC 配套组成了一套完整的开发环 境,GDB 是 Linux 和许多类 Unix 系统中的标准开发环境。

一般来说,GDB 主要帮助你完成下面四个方面的功能:

  1. 启动程序,可以按照自定义的要求随心所欲的运行程序
  2. 可让被调试的程序在所指定的调置的断点处停住(断点可以是条件表达式)
  3. 当程序被停住时,可以检查此时程序中所发生的事 4. 可以改变程序,将一个 BUG 产生的影响修正从而测试其他 BUG

安装gdb

yum install -y gdb

查看当前安装的版本

gdb --version

如果安装成功则会有以下提示信息

GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-120.el7
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.

开始debug

需要开始调试就首先要有一段测试代码

main.cpp

#include<iostream>
#include<cstring>
using namespace std;
int  main()
{

       int n;
       cin>>n;
      for(int i = 0;i<n;i++){
         cout<<i<<' ';

}
  cout<<endl;
   return 0;
}

编译带有调试信息的二进制文件

g++ -g -Wall main.cpp -o main

debug.png

其中test是不带调试信息正常编译的,通过大小就可以看出区别了。

调试断点

开始进入gdb 调试状态

gdb main

在程序入口处打断点

break main

打断点.png

在这里需要说明的是 b 是 break的缩写,所以使用 b main也是没问题的

运行程序

run

run.png

来到第一个断点处

next

继续运行

image-20230807151853931

退出调试

quit

调试结束

总结

run  r 运行程序
next  n 继续运行
list  查看源代码
info b 查看断点
b break
  函数执行的位置,在第几行打的断点
quit   退出

其他命令

print p 变量名/数组下标

print.png

finish / step 
退出函数内部  遇到函数进入函数体
start 停在程序执行的第一行

open打开文件

函数原型

int open(const char *pathName, int flags); // 打开文件
int open(const char *pathName, int flags, mode_t mode); // 创建文件

pathname:表示要打开的文件路径。
flags:用于指示打开文件的选项,常用的有O_RDONLYO_WRONLYO_RDWR

这三个选项必须有且只能有一个被指定。为什么O_RDWR!=O_RDONLY|O_WRONLY呢?Linux环境中,O_RDONLY被定义为0,O_WRONLY被定义为1,而O_RDWR却被定义为2。除了以上三个选项,Linux平台还支持更多的选项,APUE中对此也进行了介绍。
·mode:只在创建文件时需要,用于指定所创建文件的权限位(还要受到umask环境变量的影响)。

main.cpp

#include<iostream>
#include <fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<cstdio>
#include <unistd.h>

int main(int argc, char* argv[]) {
    int fd = open("test.txt", O_RDONLY);
    if (fd == -1) {
        perror("open");//把一个描述性错误消息输出到标准错误 stderr。首先输出字符串 str,后跟一个冒号,然后是一个空格。
    }
    //printf("test.txt fd = %d \n", fd);
    close(fd);
    return 0;
}

输出:

open: No such file or directory

open 新建文件

#include<iostream>
#include <fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<cstdio>
#include <unistd.h>

int main(int argc, char* argv[]) {
     int fd = open("lose.txt",O_RDWR|O_CREAT,0077);
     // 以可读模式打开或若此文件不存在则创建它。使用此选项时需要提供第三个参数mode ,表示该文件的访问权限。1+4+2 = 7 
     // == rwx 可读可写可访问 
    if(fd==-1){
       perror("open");
    }
    close(fd);

}

read、write函数

/*  
    #include <unistd.h>
    ssize_t read(int fd, void *buf, size_t count);
        参数:
            - fd:文件描述符,open得到的,通过这个文件描述符操作某个文件
            - buf:需要读取数据存放的地方,数组的地址(传出参数)
            - count:指定的数组的大小
        返回值:
            - 成功:
                >0: 返回实际的读取到的字节数
                =0:文件已经读取完了
            - 失败:-1 ,并且设置errno

    #include <unistd.h>
    ssize_t write(int fd, const void *buf, size_t count);
        参数:
            - fd:文件描述符,open得到的,通过这个文件描述符操作某个文件
            - buf:要往磁盘写入的数据,数据
            - count:要写的数据的实际的大小
        返回值:
            成功:实际写入的字节数
            失败:返回-1,并设置errno
*/
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main() {

    // 1.通过open打开english.txt文件
    int srcfd = open("english.txt", O_RDONLY);
    if(srcfd == -1) {
        perror("open");
        return -1;
    }

    // 2.创建一个新的文件(拷贝文件)
    int destfd = open("cpy.txt", O_WRONLY | O_CREAT, 0664);
    if(destfd == -1) {
        perror("open");
        return -1;
    }

    // 3.频繁的读写操作
    char buf[1024] = {0};
    int len = 0;
    while((len = read(srcfd, buf, sizeof(buf))) > 0) {
        write(destfd, buf, len);
    }

    // 4.关闭文件
    close(destfd);
    close(srcfd);


    return 0;
}

lessk

off_t lseek(int fd, off_t offset, int whence);

第一个参数是文件描述符;第二个参数是偏移量,int型的数,正数是向后偏移,负数向前偏移;第三个参数是有三个选项:
1.SEEK_SET:将文件指针偏移到传入字节数处(文件头开始偏移)
2.SEEK_CUR:将文件指针偏移到当前位置加上传入字节数处;((当前位置开始偏移)
3.SEEK_END:将文件指针偏移到文件末尾加上传入字节数处(作为拓展作用,必须再执行一次写操作)

案例

#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main() {

    int ls = open("hello.txt",O_RDWR);
    if(ls==-1){
         perror("open");
         return-1;
    }

    int ret = lseek(ls,0,SEEK_END); // 获取文件的长度
    if(ret==-1){
         perror("lseek");
         return -1;
    }
  write(ls," ", 100); // 扩展100个字节
   close(ls);
    return 0;
}

stat函数

int stat(const char *file_name, struct stat *buf)

函数说明: 通过文件名filename获取文件信息,并保存在buf所指的结构体stat中
返回值: 执行成功则返回0,失败返回-1,错误代码存于errno


这些函数返回有关文件的信息,存储在指向 statbuf 的缓冲区中。对于文件本身,不需要权限,但是对于 stat()、fstatat() 和 lstat(),需要对路径名中的所有目录具有执行(搜索)权限,这些目录是指向文件的路径中的。

stat() 和 fstatat() 用于检索路径名指向的文件的信息;fstatat() 的区别如下所述。 lstat() 与 stat() 完全相同,唯一的区别是,如果路径名是符号链接,则返回有关符号链接本身的信息,而不是符号链接所指向的文件。

fstat() 与 stat() 完全相同,唯一的区别是,要检索信息的文件由文件描述符 fd 指定。

#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
     struct stat statbuf;
     int ret = stat("a.txt",&statbuf);
     if(ret==-1){
          perror("stat");
          return -1;
     }

     printf(" size = %ld  ",statbuf.st_size); // 计算文件的大小
}

模拟实现 ls -l

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
#include <string.h>

// 模拟实现 ls -l 指令
// -rw-rw-r-- 1 nowcoder nowcoder 12 12月  3 15:48 a.txt
int main(int argc, char * argv[]) {

    // 判断输入的参数是否正确
    if(argc < 2) {
        printf("%s filename\n", argv[0]);
        return -1;
    }

    // 通过stat函数获取用户传入的文件的信息
    struct stat st;
    int ret = stat(argv[1], &st);
    if(ret == -1) {
        perror("stat");
        return -1;
    }

    // 获取文件类型和文件权限
    char perms[11] = {0};   // 用于保存文件类型和文件权限的字符串

    switch(st.st_mode & S_IFMT) {  
        case S_IFLNK:
            perms[0] = 'l';
            break;
        case S_IFDIR:
            perms[0] = 'd';
            break;
        case S_IFREG:
            perms[0] = '-';
            break; 
        case S_IFBLK:
            perms[0] = 'b';
            break; 
        case S_IFCHR:
            perms[0] = 'c';
            break; 
        case S_IFSOCK:
            perms[0] = 's';
            break;
        case S_IFIFO:
            perms[0] = 'p';
            break;
        default:
            perms[0] = '?';
            break;
    }

    // 判断文件的访问权限

    // 文件所有者
    perms[1] = (st.st_mode & S_IRUSR) ? 'r' : '-';
    perms[2] = (st.st_mode & S_IWUSR) ? 'w' : '-';
    perms[3] = (st.st_mode & S_IXUSR) ? 'x' : '-';

    // 文件所在组
    perms[4] = (st.st_mode & S_IRGRP) ? 'r' : '-';
    perms[5] = (st.st_mode & S_IWGRP) ? 'w' : '-';
    perms[6] = (st.st_mode & S_IXGRP) ? 'x' : '-';

    // 其他人
    perms[7] = (st.st_mode & S_IROTH) ? 'r' : '-';
    perms[8] = (st.st_mode & S_IWOTH) ? 'w' : '-';
    perms[9] = (st.st_mode & S_IXOTH) ? 'x' : '-';

    // 硬连接数
    int linkNum = st.st_nlink;

    // 文件所有者
    char * fileUser = getpwuid(st.st_uid)->pw_name;

    // 文件所在组
    char * fileGrp = getgrgid(st.st_gid)->gr_name;

    // 文件大小
    long int fileSize = st.st_size;

    // 获取修改的时间
    char * time = ctime(&st.st_mtime);

    char mtime[512] = {0};
    strncpy(mtime, time, strlen(time) - 1);

    char buf[1024];
    sprintf(buf, "%s %d %s %s %ld %s %s", perms, linkNum, fileUser, fileGrp, fileSize, mtime, argv[1]);

    printf("%s\n", buf);

    return 0;
}

文件属性操作函数

 int access(const char* __name, int __type)       // 判断文件是否存在
 int chmod(const char *file, __mode_t mode)      // 修改文件的权限 fine : 需要修改文件的路径 
  int truncate(const char *path, off_t length);  // 修改文件大小

案例

#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main()
{
      int ls = access("a.txt", F_OK);
       if(ls==-1){
            perror("access");
            return -1;
       }
       printf("文件存在\n");

      int ret =  chmod("a.txt",0777);
      ret == -1? perror("chmod"):printf("权限已更改!\n");

    int l = truncate("a.txt",29);
     l==-1? perror("truncast"):printf("大小已更改!\n");

    }

输出

root@dcs-1e648dbe-0:/workspace/Linux# ls -lh
total 30K
-rwxrwxrwx 1 root root   10 Aug  8 09:13 a.txt
-rw-r--r-- 1 root root  497 Aug  8 09:13 acces.c
-rwxr-xr-x 1 root root    5 Aug  8 03:21 b.txt
-rw-r--r-- 1 root root    8 Aug  8 08:45 compile_flags.txt
-rw-r--r-- 1 root root  431 Aug  8 04:45 hello.c
-rw-r--r-- 1 root root 7.6K Aug  8 04:45 hello.txt
-rw-r--r-- 1 root root  264 Aug  8 08:46 ls-l.c
-rwxr-xr-x 1 root root  17K Aug  8 07:04 main
-rw-r--r-- 1 root root 1.1K Aug  8 03:21 main.c
-rw-r--r-- 1 root root  326 Aug  8 07:02 stat.c
root@dcs-1e648dbe-0:/workspace/Linux# ls -lh
total 30K
-rwxrwxrwx 1 root root   29 Aug  8 09:13 a.txt   // 文件大小发生了改变
-rw-r--r-- 1 root root  497 Aug  8 09:13 acces.c
-rwxr-xr-x 1 root root    5 Aug  8 03:21 b.txt
-rw-r--r-- 1 root root    8 Aug  8 08:45 compile_flags.txt
-rw-r--r-- 1 root root  431 Aug  8 04:45 hello.c
-rw-r--r-- 1 root root 7.6K Aug  8 04:45 hello.txt
-rw-r--r-- 1 root root  264 Aug  8 08:46 ls-l.c
-rwxr-xr-x 1 root root  17K Aug  8 07:04 main
-rw-r--r-- 1 root root 1.1K Aug  8 03:21 main.c
-rw-r--r-- 1 root root  326 Aug  8 07:02 stat.c
root@dcs-1e648dbe-0:/workspace/Linux#

目录属性操作函数

int mkdir(const char* __path, __mode_t __mode)  // 新建文件夹的名字  所授予的权限
int chdir(const char * path) // 修改进程运行时的工作目录
char*getbuf(const char*path,size_t size) // 获取当前工作目录

案例

#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main()
{
    int ref = rename("abc","鸡你太美");
    ref==-1? perror("mkdir"):printf("修改成功\n"); 
    char buf[128] = {0};
    getcwd(buf, sizeof(buf));
    printf("当前的工作目录是: %s\n", buf);
    int ret = chdir("/workspace/Linux/abc");
    ret == -1 ? perror("chdir") : printf("修改成功!\n");

    int fd = open("c.txt", O_CREAT | O_RDONLY, 0664);
    fd == -1 ? perror("open") : printf("新文件创建成功!\n");

    getcwd(buf, sizeof(buf));
    printf("当前的工作目录是: %s\n", buf);
    close(fd);
}

首先,代码尝试将文件"abc"重命名为"鸡你太美",使用rename函数来实现。如果重命名失败,它会打印错误消息并退出程序。如果重命名成功,它会打印"修改成功"的消息。

接下来,代码获取当前的工作目录,并使用getcwd函数将其存储在名为buf的字符数组中。然后,它使用printf函数输出当前的工作目录。

接着,代码使用chdir函数将当前的工作目录修改为"/workspace/Linux/abc"。如果修改失败,它会打印错误消息并退出程序。如果修改成功,它会打印"修改成功"的消息。

然后,代码使用open函数创建一个名为"c.txt"的新文件,打开模式为O_CREAT(创建新文件)和O_RDONLY(只读模式)。如果创建新文件失败,它会打印错误消息并退出程序。如果创建新文件成功,它会打印"新文件创建成功"的消息。

最后,代码再次获取当前的工作目录,并使用close函数关闭文件描述符fd

目录遍历函数

dup,dup2函数

函数定义

int dup(int oldfd) // 复制一个新的文件描述符
int dup2(int oldfd, int newfd)     // 重定向描述符

dup2与dup区别是dup2可以用参数newfd指定新文件描述符的数值。若参数newfd已经被程序使用,则系统就会将newfd所指的文件关闭,若newfd等于oldfd,则返回newfd,而不关闭newfd所指的文件。dup2所复制的文件描述符与原来的文件描述符共享各种文件状态。共享所有的锁定,读写位置和各项权限或flags等

dup.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/time.h>
int main(int argc, char *argv[]) {
    int fd = open("a.txt",O_RDWR|O_CREAT,0664);
    if(fd==-1){
         perror("open");
         exit(-1);
    }
    int fde = dup(fd);
    if(fde == -1){
         perror("dup");
         return -1;
    }

   printf("%d %d ",fd,fde);
    const char*t = "world";
    int ret = write(fd, t,strlen(t));
     if(ret==-1){
          perror("write");
          return -1;
     }

    close(fd);
    close(fde);
}

dup2.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/time.h>

int main() {
     int fd = open("test.txt",O_RDWR|O_CREAT,0664);
     if(fd==-1){
          perror("open");
          exit(-1);
     }

     int fd1 = open("test1.txt",O_RDWR|O_CREAT,0664);
     if(fd1==-1){
          perror("open");
          exit(-1);
     }
    printf("%d %d \n",fd,fd1); // 3 4 

    int fd2 = dup2(fd,  fd1); // 返回新的描述符
    if(fd2==-1){
          perror("open");
          exit(-1);
     }
    printf("%d %d %d \n",fd,fd1,fd2); // 输出 3 4 4 
}

fcntl函数

函数声明

fcntl(int fd, int cmd, ...)

返回值:
正确返回值根据命令码而定,错误返回-1。

/*

    #include <unistd.h>
    #include <fcntl.h>

    int fcntl(int fd, int cmd, ...);
    参数:
        fd : 表示需要操作的文件描述符
        cmd: 表示对文件描述符进行如何操作
            - F_DUPFD : 复制文件描述符,复制的是第一个参数fd,得到一个新的文件描述符(返回值)
                int ret = fcntl(fd, F_DUPFD);

            - F_GETFL : 获取指定的文件描述符文件状态flag
              获取的flag和我们通过open函数传递的flag是一个东西。

            - F_SETFL : 设置文件描述符文件状态flag
              必选项:O_RDONLY, O_WRONLY, O_RDWR 不可以被修改
              可选性:O_APPEND, O)NONBLOCK
                O_APPEND 表示追加数据
                NONBLOK 设置成非阻塞

        阻塞和非阻塞:描述的是函数调用的行为。
*/

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

int main() {

    // 1.复制文件描述符
    // int fd = open("1.txt", O_RDONLY);
    // int ret = fcntl(fd, F_DUPFD);

    // 2.修改或者获取文件状态flag
    int fd = open("1.txt", O_RDWR);
    if(fd == -1) {
        perror("open");
        return -1;
    }

    // 获取文件描述符状态flag
    int flag = fcntl(fd, F_GETFL);
    if(flag == -1) {
        perror("fcntl");
        return -1;
    }
    flag |= O_APPEND;   // flag = flag | O_APPEND

    // 修改文件描述符状态的flag,给flag加入O_APPEND这个标记
    int ret = fcntl(fd, F_SETFL, flag);
    if(ret == -1) {
        perror("fcntl");
        return -1;
    }

    char * str = "nihao";
    write(fd, str, strlen(str));

    close(fd);

    return 0;
}
相关实践学习
CentOS 7迁移Anolis OS 7
龙蜥操作系统Anolis OS的体验。Anolis OS 7生态上和依赖管理上保持跟CentOS 7.x兼容,一键式迁移脚本centos2anolis.py。本文为您介绍如何通过AOMS迁移工具实现CentOS 7.x到Anolis OS 7的迁移。
目录
相关文章
|
11天前
|
Linux 应用服务中间件 Shell
linux系统服务二!
本文详细介绍了Linux系统的启动流程,包括CentOS 7的具体启动步骤,从BIOS自检到加载内核、启动systemd程序等。同时,文章还对比了CentOS 6和CentOS 7的启动流程,分析了启动过程中的耗时情况。接着,文章讲解了Linux的运行级别及其管理命令,systemd的基本概念、优势及常用命令,并提供了自定义systemd启动文件的示例。最后,文章介绍了单用户模式和救援模式的使用方法,包括如何找回忘记的密码和修复启动故障。
32 5
linux系统服务二!
|
11天前
|
Linux 应用服务中间件 Shell
linux系统服务!!!
本文详细介绍了Linux系统(以CentOS7为例)的启动流程,包括BIOS自检、读取MBR信息、加载Grub菜单、加载内核及驱动程序、启动systemd程序加载必要文件等五个主要步骤。同时,文章还对比了CentOS6和CentOS7的启动流程图,并分析了启动流程的耗时。此外,文中还讲解了Linux的运行级别、systemd的基本概念及其优势,以及如何使用systemd管理服务。最后,文章提供了单用户模式和救援模式的实战案例,帮助读者理解如何在系统启动出现问题时进行修复。
32 3
linux系统服务!!!
|
19天前
|
Web App开发 搜索推荐 Unix
Linux系统之MobaXterm远程连接centos的GNOME桌面环境
【10月更文挑战第21天】Linux系统之MobaXterm远程连接centos的GNOME桌面环境
159 4
Linux系统之MobaXterm远程连接centos的GNOME桌面环境
|
21天前
|
Linux 测试技术 网络安全
Linux系统之安装OneNav个人书签管理器
【10月更文挑战第19天】Linux系统之安装OneNav个人书签管理器
40 5
Linux系统之安装OneNav个人书签管理器
|
20天前
|
运维 监控 Linux
Linux系统之部署Linux管理面板1Panel
【10月更文挑战第20天】Linux系统之部署Linux管理面板1Panel
66 3
Linux系统之部署Linux管理面板1Panel
|
1天前
|
Linux
在 Linux 系统中,“cd”命令用于切换当前工作目录
在 Linux 系统中,“cd”命令用于切换当前工作目录。本文详细介绍了“cd”命令的基本用法和常见技巧,包括使用“.”、“..”、“~”、绝对路径和相对路径,以及快速切换到上一次工作目录等。此外,还探讨了高级技巧,如使用通配符、结合其他命令、在脚本中使用,以及实际应用案例,帮助读者提高工作效率。
10 3
|
1天前
|
安全 网络协议 Linux
本文详细介绍了 Linux 系统中 ping 命令的使用方法和技巧,涵盖基本用法、高级用法、实际应用案例及注意事项。
本文详细介绍了 Linux 系统中 ping 命令的使用方法和技巧,涵盖基本用法、高级用法、实际应用案例及注意事项。通过掌握 ping 命令,读者可以轻松测试网络连通性、诊断网络问题并提升网络管理能力。
8 3
|
4天前
|
安全 Linux 数据安全/隐私保护
在 Linux 系统中,查找文件所有者是系统管理和安全审计的重要技能。
在 Linux 系统中,查找文件所有者是系统管理和安全审计的重要技能。本文介绍了使用 `ls -l` 和 `stat` 命令查找文件所有者的基本方法,以及通过文件路径、通配符和结合其他命令的高级技巧。还提供了实际案例分析和注意事项,帮助读者更好地掌握这一操作。
15 6
|
1天前
|
监控 安全 Linux
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景,包括 ping(测试连通性)、traceroute(跟踪路由路径)、netstat(显示网络连接信息)、nmap(网络扫描)、ifconfig 和 ip(网络接口配置)。掌握这些命令有助于高效诊断和解决网络问题,保障网络稳定运行。
9 2
|
4天前
|
Linux
在 Linux 系统中,`find` 命令是一个强大的文件查找工具
在 Linux 系统中,`find` 命令是一个强大的文件查找工具。本文详细介绍了 `find` 命令的基本语法、常用选项和具体应用示例,帮助用户快速掌握如何根据文件名、类型、大小、修改时间等条件查找文件,并展示了如何结合逻辑运算符、正则表达式和排除特定目录等高级用法。
21 5