Linux系统编程入门(下)

Makefile

  • 文件命名及规则
    image
app:sub.c add.c mult.c div.c main.c
    gcc sub.c add.c mult.c div.c main.c -o app
  • 检查更新
    image
app:sub.o add.o mult.o div.o main.o
    gcc sub.o add.o mult.o div.o main.o -o app

sub.o:sub.c
    gcc -c sub.c -o sub.o

add.o:add.c
    gcc -c add.c -o add.o

mult.o:mult.c
    gcc -c mult.c -o mult.o

div.o:div.c
    gcc -c div.c -o div.o
    
main.o:main.c
    gcc -c main.c -o main.o

image

  • 变量
#定义变量
src=sub.o add.o mult.o div.o main.o
target=app
$(target):$(src)
    $(CC) $(src) -o $(target)
    
sub.o:sub.c
    gcc -c sub.c -o sub.o

add.o:add.c
    gcc -c add.c -o add.o

mult.o:mult.c
    gcc -c mult.c -o mult.o

div.o:div.c
    gcc -c div.c -o div.o
    
main.o:main.c
    gcc -c main.c -o main.o
  • 模式匹配
    image
#定义变量
src=sub.o add.o mult.o div.o main.o
target=app
$(target):$(src)
$(CC) $(src) -o $(target)
%.o:%.c
$(CC) -c $< -o $@
  • wildcard函数
    image

    获取目标目录下的.c文件

src=$(wildcard ./*.c)
  • patsubst函数
    image

在目标文本text里找到符合模式pattern的文本,如果匹配的话使用replacement来替换,返回被替换后的字符串

#定义变量
src=$(wildcard ./*.c)
#src=sub.o add.o mult.o div.o main.o
#在src这个文本字符串里将.c后缀,修改为.o后缀
objs=$(patsubst %.c, %.o, $(src))
target=app
$(target):$(objs)
    $(CC) $(objs) -o $(target)
%.o:%.c
    $(CC) -c $< -o $@
  
  • clean

    规则定义:在Makefile中添加clean规则用于删除编译生成的中间文件(.o文件),其基本语法为:
    参数说明:-f参数表示强制删除,避免因文件不存在而报错
    clean文件冲突问题: 当目录中存在名为clean的文件时,执行make clean会提示"clean已是最新"而不执行删除操作,
    解决方法是在Makefile中添加.PHONY: clean声明clean为伪目标,伪目标不会生成实际文件,因此不会与同名文件冲突。

src=$(wildcard ./*.c)
objs=$(patsubst %.c, %.o, $(src))
target=app
$(target):$(objs)
    $(CC) $(objs) -o $(target)
%.o:%.c
    $(CC) -c $< -o $@

#定义clean是一个尾目标。表示clean不需要生成文件
.PHONY:clean
clean:
    rm $(objs) -f

GDB

image
image
image

Linux系统IO函数

IO函数

image

open

  • 打开文件
/*
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>

    //打开一个已经存在的文件
    int open(const char *pathname, int flags);
        参数:
            - pathname:要打开文件的路径
            - flags:对文件的操作权限设置还有其他的设置
            O_RDONLY, O_WRONLY, or O_RDWR 这三个只能选一个
        返回值:返回一个新的文件描述符,如果调用失败,返回-1

    errno: 属于Linux系统函数库,库里面的一个全局变量,记录的是最近的错误号。

    #include <stdio.h>
    void perror(const char *s);
        s参数:用户描述,比如hello,最终输出的内容是  hello:xxx(实际的错误信息)
    作用:打印errno对应的错误描述


    //创建一个新的文件
    int open(const char *pathname, int flags, mode_t mode);
*/
  • 示例
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

int main() {
    int fd = open("a.txt", O_RDONLY);
    if(fd == -1) {
        perror("open");
    }
    
    //关闭
    close(fd);
    return 0;
}
  • 创建新文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags, mode_t mode);
    参数:
        - pathname: 要创建的文件的路径
        - flags: 对文件操作的权限和其他的设置
            - 必选项: O_RDONLY, O_WRONLY, or O_RDWR 互斥的
            - 可选项: O_CREAT 文件不存在,创建新文件
            - mode: 八进制的数,表示创建出的新的文件的操作权限,比如: 0775 (0代表八进制,7代表用户权限,7代表用户所在组的权限,5代表其他组的用户权限)
            最终的权限是: mode & ~umask
                umask的作用是抹去某些权限
                umask 与 当前用户有关
                ~umask = 0777-umask = 0777-0002 = 0775
                最终的权限是: 0777 & 0775 = 0775
  • 示例
 #include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

int main() {
    //创建一个新的文件
    int fd = open("create.txt", O_RDWR | O_CREAT, 0777);
    if(fd == -1) {
        perror("open");
    }
    //关闭
    close(fd);
    return 0;
}

close

#include <unistd.h>
int close(int fd);

read与write

man 2 read
man 2 write

  • read
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
        参数:
            - fd: 文件描述符, open得到的,通过文件描述符来操作某个文件
            - buf: 需要读取数据存放的地方,数组的地址(传出参数)
            - count: 指定的数组的大小
        返回值:
            - 成功:
                >0 : 返回实际的读取到的字节数
                =0 : 文件已经读取完了
            - 失败:
                -1 : 读取失败并设置errorno
  • write
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
        参数:
            - fd: 文件描述符, open得到的,通过文件描述符来操作某个文件
            - buf: 要往磁盘写入的数据
            - count: 要写的的数据的实际大小
        返回值:
            成功:实际写入的字节数
            失败: -1, 写入失败并设置errorno
  • 实现拷贝文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main() {
    // 1.通过open, 只读O_RDONLY打开english.txt文件
    int srcfd = open("english.txt", O_RDONLY);
    if(srcfd == -1) {
        perror("open");
    }
    // 2.创建一个新的文件(拷贝文件)
    int destfd = open("cpy.txt", O_WRONLY | O_CREAT, 0664);
    if(destfd == -1) {
        perror("open");
    }
    // 3.频繁的读写操作
    char buf[1024] = {0};
    // int len = read(srcfd, buf, sizeof(buf));
    // printf("%s", buf);
    int len = 0;
    while((len = read(srcfd, buf, sizeof(buf))) > 0) {
        write(destfd, buf, len);
    }
    // 4.关闭文件
    close(srcfd);
    close(destfd);
    return 0;
}

lseek

标准C库的函数 man 3 fseek
#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);

Linux系统函数 man 2 lseek
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
    参数:
        - fd: 文件描述符,通过open得到的,通过这个fd操作某个文件
        - offset: 偏移量
        - whence:
            SEEK_SET
                设置文件指针的偏移量

            SEEK_CUR
                设置偏移量:当前位置 + 第二个参数offset的值

            SEEK_END
                设置偏移量:文件大小 + 第二个参数offset的值
    返回值:返回文件指针的位置
  • 作用

    1.移动文件指针到头文件
    lseek(fd, 0, SEEK_SET)

    2.用来获取当前文件指针
    lseek(fd, 0, SEEK_CUR)

    3.获取文件长度
    lseek(fd, 0, SEEK_END)

    4.拓展文件的长度: eg.当前文件10b, 110b, 增加了100个字节
    lseek(fd, 100, SEEK_END)
    注意要写入一次数据才生效

  • 实现扩展文件大小

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main() {
   int fd = open("hello.txt", O_RDWR);
   if(fd == -1) {
       perror("open");
   }
   
   //扩展文件长度
   int ret = lseek(fd, 100, SEEK_END);
   if(ret == -1) {
       perror("lseek");
   }
   //11+100+1=112
   write(fd, " ", 1);
   //关闭文件
   close(fd);
   return 0;
}

stat与lstat

man 2 stat

  • stat作用

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

int stat(const char *pathname, struct stat *statbuf);
    作用: 获取一个文件相关的信息
    参数: 
        - pathname: 文件路径
        - statbuf: 结构体变量,传出参数,用于保存获取到的文件信息
    返回值:
        成功:返回0
        失败:返回-1 设置errno

int lstat(const char *pathname, struct stat *statbuf);
    作用: 获取一个文件相关的信息
    参数: 
        - pathname: 文件路径
        - statbuf: 结构体变量,传出参数,用于保存获取到的文件信息
    返回值:
        成功:返回0
        失败:返回-1 设置errno
  • st_mode变量
    image

  • stat示例

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
int main() {
    struct stat statbuf;
    int ret = stat("a.txt", &statbuf);
    if(ret == -1) {
        perror("stat");
    }
    printf("size: %ld\n", statbuf.st_size);
    return 0;
}

  • 软链接:
    ln -s a.txt b.txt
    image

  • stat与lstat的区别

当文件是一个符号链接时,
lstat返回的是该符号链接本身的信息;
而stat返回的是该链接指向的文件的信息。

实现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 chan chan    11  9月  6 17:51 a.txt
int main(int argc, char *argv[]) {
    //判断输入的参数是否正确
    if(argc < 2) {
        printf("%s filename\n", argv[0]);
        return 0;
    }

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

    //1. 获取文件类型与文件权限
    char perms[11] = {0}; //用于保存文件类型与文件权限
    switch (statbuf.st_mode & S_IFMT) {
        case S_IFLNK:
            perms[0] = 'l';
            break;
        case S_IFREG:
            perms[0] = '-';
            break;
        case S_IFBLK:
            perms[0] = 'b';
            break;
        case S_IFDIR:
            perms[0] = 'd';
            break;
        case S_IFCHR:
            perms[0] = 'c';
            break;
        case S_IFIFO:
            perms[0] = 'p';
            break;
        case S_IFSOCK:
            perms[0] = 's';
            break;
        default:
            perms[0] = '?';
            break;
    }
    
    // 判断文件的访问权限
    perms[1] = (statbuf.st_mode & S_IRUSR) ? 'r':'-';
    perms[2] = (statbuf.st_mode & S_IWUSR) ? 'w':'-';
    perms[3] = (statbuf.st_mode & S_IXUSR) ? 'x':'-';
    perms[4] = (statbuf.st_mode & S_IRGRP) ? 'r':'-';
    perms[5] = (statbuf.st_mode & S_IWGRP) ? 'w':'-';
    perms[6] = (statbuf.st_mode & S_IXGRP) ? 'x':'-';
    perms[7] = (statbuf.st_mode & S_IROTH) ? 'r':'-';
    perms[8] = (statbuf.st_mode & S_IWOTH) ? 'w':'-';
    perms[9] = (statbuf.st_mode & S_IXOTH) ? 'x':'-';

    // 2.硬连接数
    int linknum = statbuf.st_nlink;

    // 3.文件所有者: 可以通过statbuf.st_uid得到uid, 然后通过getpwuid来获取用户名; man 3 getpwuid可查看
    char * fileUser = getpwuid(statbuf.st_uid)->pw_name;

    // 4.文件所在组: 可以通过statbuf.st_gid得到gid, 然后通过getgrgid来获取用户名; man 3 getpwgid可查看
    char * fileGrp = getgrgid(statbuf.st_gid)->gr_name;

    // 5.获取大小
    long int fileSize = statbuf.st_size;

    // 6.获取修改时间  statbuf.st_mtime:从1980年到修改时间的一个秒数,使用ctime转换,以换行结尾
    char * time = ctime(&statbuf.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;
}

文件操作函数

文件属性操作函数

access

man 2 access

#include <unistd.h>
int access(const char *pathname, int mode);
    作用:判断某个文件是否有某个权限(R_OK,W_OK,X_OK),或者判断文件是否存在(F_OK)
    参数:
        - pathname: 判断的文件路径
        - mode:
            R_OK,W_OK,X_OK,F_OK 读,写,执行,文件存在
    返回值:成功返回0,失败返回1
  • 示例代码
#include <unistd.h>
#include <stdio.h>

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

chmod

#include <sys/stat.h>
int chmod(const char *pathname, mode_t mode);
    作用:修改文件的权限
    参数:
        - pathname: 修改的文件的路径
        - mode: 需要修改的权限值,八进制数
    返回值:
        On success, zero is returned.  On error, -1 is returned, and errno is set appropriately.
	  
  • 示例代码
#include <sys/stat.h>
#include <stdio.h>
int main() {
    int ret = chmod("a.txt", 0775);
    if(ret == -1) {
        perror("chmod");
        return -1;
    }
    printf("修改成功!\n");
    return 0;
}

chown

改变用户所属组
sudo useradd 用户名 添加用户
/vim/passwd 可查看所有用户
/vim/group 可查看每个用户的组情况
id 用户名 查看用户所属组信息
#include <unistd.h>
int chown(const char *pathname, uid_t owner, gid_t group);
作用:让目标用户,uid 归属于gid下

truncate

#include <unistd.h>
#include <sys/types.h>
int truncate(const char *path, off_t length);
    作用:缩减或者扩展文件的尺寸至指定的大小
    参数:
        - path: 需要修改的文件的路径
        - length: 最终文件需要变成的大小
    返回值:
        On success, zero is returned.  On error, -1 is returned, and errno is set appropriately.
  • 示例代码
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
int main() {
    int ret = truncate("b.txt", 5);
    if(ret == -1) {
        perror("truncate");
        return -1;
    }
    printf("修改文件大小成功\n");
    return 0;
}

目录操作函数

mkdir

创建文件夹

#include <sys/stat.h>
#include <sys/types.h>
int mkdir(const char *pathname, mode_t mode);
    作用:创建一个目录
    参数:
        pathname: 创建的目录的路径
        mode: 权限,八进制的数
    返回值:
        return zero on success, or -1 if an error occurred (in which case, errno is set appropriately).
  • 示例代码
#include <sys/stat.h>
#include <sys/types.h>
#include <stdio.h>
int main() {
    int ret = mkdir("a", 0777);
    if(ret == -1) {
        perror("mkdir");
        return -1;
    }
    printf("创建文件夹成功\n");
    return 0;
}

rmdir

删除目录

rename

修改名字

#include <stdio.h>

int rename(const char *oldpath, const char *newpath);
返回值
On success, zero is returned.  On error, -1 is returned, and errno is set appropriately.
  • 示例代码
#include <stdio.h>
int main() {
    int ret = rename("a","b");
    if(ret == -1) {
        perror("rename");
        return -1;
    }
    printf("修改成功\n");
    return 0;
}

chdir与getcwd

#include <unistd.h>
int chdir(const char *path);
    作用:修改进程的工作目录
        比如在/home/chan 启动了一个可执行的程序a.out,进程的工作目录 /home/chan
    参数:
        path: 需要修改的工作目录
    返回值:
        On success, zero is returned.  On error, -1 is returned, and errno is set appropriately.

#include <unistd.h>
char *getcwd(char *buf, size_t size);
    作用:获取当前工作目录
    参数:
        - buf: 存储的路径,指向一个数组(传出参数)
        - size: 数组大小
    返回值:
        返回指向的一块内存,这个数据就是第一个参数
  • 示例代码
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main() {
    char buf[128];
    getcwd(buf, sizeof(buf));
    printf("当前的工作目录是:%s\n", buf);

    //修改工作目录
    int ret = chdir("b");
    if(ret == -1) {
        perror("chdir");
        return -1;
    }
    printf("修改后的工作目录是:b\n");

    //创建一个新文件
    int fd = open("chdir.txt", O_WRONLY|O_CREAT, 0664);
    if(fd == -1) {
        perror("open");
        return -1;
    }
    close(fd);

    //获取当前的工作路径

    char buff[128];
    getcwd(buff, sizeof(buff));
    printf("当前的工作目录是:%s\n", buff);
    return 0;
}

目录遍历函数

opendir

man 3 opendir


// 打开一个目录
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
    参数:
        - name: 打开的目录名
    返回:
        DIR * 类型, 一个目录流
        失败返回NULL

readdir

man 3 readdir


// 读取目录的数据
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
    - 参数: dirp是opendir返回的目录流
    - 返回:
        dirent结构体, 代表读取到的文件信息
        读取到了末尾或者失败了返回NULL
        struct dirent {
            ino_t          d_ino;       
            off_t          d_off;       
            unsigned short d_reclen;    
            unsigned char  d_type;      
            char           d_name[256]; 
        };

closedir

man 3 closedir

// 关闭目录
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);

递归获取目录下的普通文件个数


#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

//读取某个目录下的所有普通文件的个数
int FileNum(const char *path);
int main(int argc, char *argv[]) {
    if(argc < 2) {
        printf("%s path\n", argv[0]);
        return -1;
    }

    printf("普通文件个数为%d\n", FileNum(argv[1]));

    return 0;
}

// 用于获取目录下所有所有普通文件的个数
int FileNum(const char *path) {
    int cnt = 0;
    // 1.打开目录
    DIR *dir = opendir(path);
    if(dir == NULL) {
        perror(NULL);
        exit(0);
    }
    // 2.循环读取dir
    struct dirent *ptr;
    while((ptr=readdir(dir)) != NULL) {
        // 获取名称 (目的是过滤掉 ./ 和 ../)
        char *dname = ptr->d_name;
        if(strcmp(dname,".") == 0||strcmp(dname,"..") == 0) {
            continue;
        }
        // 判断是普通文件还是目录

        // 如果是目录
        if(ptr->d_type == DT_DIR) {
            // 递归读取这个目录, 需要在path后加上/dname
            char newpath[256];
            sprintf(newpath, "%s/%s", path, dname);
            cnt += FileNum(newpath);
        }

        // 如果是普通文件
        if(ptr->d_type == DT_REG) {
            cnt ++;
        }
    }
    closedir(dir);
    return cnt;
}

dup与fcntl

dup

#include <unistd.h>
int dup(int oldfd);
      作用:复制一个新的文件描述符,与传入的旧的文件描述符oldfd指向同一个文件
          例子:
          fd=3, int fd1 = dup(fd),
          fd指向的是a.txt,fd1指向的也是a.txt
          从空闲的文件描述符表中找一个最小的,作为新的拷贝文件
  • 示例代码
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>

int main() {
    int fd = open("a.txt", O_RDWR | O_CREAT, 0644);
    int fd1 = dup(fd);
    if(fd1 == -1) {
        perror("dup");
        return -1;
    }
    printf("fd=%d fd1=%d\n", fd, fd1);
    close(fd);

    char *str = "Hello, World";
    int ret = write(fd1, str, strlen(str));
    if(ret == -1) {
        perror("write");
        return -1;
    }
    close(fd1);
    return 0;
}

dup2

#include <unistd.h>
int dup2(int oldfd, int newfd);
作用:重定向文件描述符
    eg: oldfd指向a.txt, newfd指向b.txt
    调用函数成功后, newfd 和 b.txt做close, newfd指向了a.txt
    oldfd 必须是一个有效的文件描述符
    oldfd和newfd值相同,相当于什么也没有做
  • 示例代码
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>

int main() {
    int fd = open("1.txt", O_RDWR | O_CREAT, 0664);
    if(fd == -1) {
        perror("open");
    }
    
    int fd1 = open("2.txt", O_RDWR | O_CREAT, 0664);
    
    printf("fd=%d fd1=%d\n", fd, fd1);
    
    int fd2 = dup2(fd, fd1);
    if(fd2 == -1) {
        perror("dup2");
    }

    // 通过fd1去写数据,实际操作的是1.txt,而不是2.txt
    char *str = "Hello, World!";
    int ret =write(fd1, str, strlen(str));
    if(ret == -1) {
        perror("write");
    }
    printf("fd=%d fd1=%d fd2=%d\n", fd, fd1, fd2);

    close(fd);
    close(fd1);
    return 0;
}

fcntl

 #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(O_RDWR | O_CREATE)是一个东西
              
              - F_SETFL : 设置文件描述符文件状态flag
                  必选项: O_WRONLY, O_WRONLY, O_RDWR 不可以被修改
                  可选项: O_APPEND, O_NONBLOCK
                      O_APPEND 表示追加数据
                      O_NONBLOCK 表示设置非堵塞
          阻塞和非阻塞: 描述的是函数调用的行为。 
  • 示例代码
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>

int main() {

    // 1.复制文件描述符
    // int fd = open("1.txt", O_RDONLY | O_CREAT);
    // int fd1 = fcntl(fd, F_DUPFD);
    // printf("%d %d\n", fd, fd1);

    // 2.修改或者获取文件状态
    int fd = open("1.txt", O_RDWR);
    if(fd == -1) {
        perror("open");
        return -1;
    }
    // 获取文件描述符状态flag
    int flag = fcntl(fd, F_GETFL);
    // 修改文件描述符状态的flag, 给flag加入O_APPEND标记
    int ret = fcntl(fd, F_SETFL, flag | O_APPEND);
    if(ret == -1) {
        perror("fcntl");
        return -1;
    }
    char *str="nihao!";
    write(fd, str, strlen(str));
    close(fd);
    return 0;
}
posted @ 2025-09-07 19:27  Xiaomostream  阅读(35)  评论(0)    收藏  举报