系统编程概述

      操作系统的职责:

            操作系统用来管理所有的资源,并将不同设备和不同程序关联起来

      Linux 系统编程:

            在Linux操作系统的环境下编程, 并使用该操作系统提供的系统调用及各种库, 对系统资源进行访问

系统调用概述

     类 UNIX 系统的软件层次:

      系统调用是 操作系统提供给用户程序的一组“ 特殊” 函数接口
      用户程序可通过 该组接口获得 操作系统( 内核) 提供的服务
例子:

    用户可通过文件系统相关的系统调用, 请求系统打开文件、 关闭文件或读写文件

 

   系统调用按功能逻辑:

        进程控制、 进程间通信、文件系统控制、系统控制、内存管理、网络管理、socket 控制、用户管理

   系统调用的返回值:
            成功: 0

            失败:负值

            错误信息存放在全局变量 errno,可用 perror 函数打印出错信息

    系统调用遵循的规范

           在 Linux 中, 应用程序编程接口(API)遵循 POSIX 标准

          POSIX 标准基于当时现有的 UNIX 实践和经验, 描述了 操作系统的系统调用编程接口(实际上就是 API), 用
于保证应用程序可以在源代码一级上在多种操作系统上移植运行

   如:

       linux 下写的 open、 write 、 read 可以直接移植到 unix 操作系统下

系统调用 I/O 函数  

       系统调用中操作 I/O 的函数, 都是针对文件描述符

       通过文件描述符可以直接对应的文件进行操作

    如:

           open、 close、 write 、 read、 ioctl

    文件描述符

          文件描述符是非负整数

          打开现存文件或新建文件时,系统(内核) 会返回一个文件描述符

          文件描述符用来指定 已打开的文件

// 程序运行后这三个文件描述符是默认打开
#define    STDIN_FILENO    0    //标准输入的文件描述符
#define    STDOUT_FILENO   1    //标准输出的文件描述符
#define    STDERR_FILENO   2    //标准错误的文件描述符

    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  只写方式打开
 *       O_RDWR    可读可写方式打开
 *  下列值 位或
 *       O_CREAT     文件不存在则创建文件,mode说明文件权限
 *       O_EXCL      指定O_CREAT,且文件已存在 则出错
 *       O_TRUNC     文件存在 清除文件内容
 *       O_APPEND    写文件,数据添加到文件末尾
 *       O_NONBLOCK  打开的文件是FIFO,字符文件,块文件,该选项为非堵塞标志位
 * mode:文件权限(可读,可写,可执行)的设置
 *       取值       八进制      含义
 *       S_IRWXU    700       文件所有者的读,写,可执行权限
 *       S_IRUSR    400       文件所有者的读权限
 *       S_IWUSR    200       文件所有者的写权限
 *       S_IXUSR    100       文件所有者的可执行权限
 *       S_IRWXG    70        文件所有者同组用户的读写可执行权限
 *       S_IRGRP    40        文件所有者同组用户的读权限
 *       
 * return:成功:文件描述符 失败:-1,可用 perror 查看原因
 */
int open(const char *pathname, int flags, mode_t mode);

    close 函数

       关闭一个文件

#include <unistd.h>

/*
 *fd:调用open打开文件返回的文件描述符
 * return:成功:0 失败:-1 可用 perror 去查看原因
 */
int close(int fd);

    write 函数

        指定数目的数据写到文件中

#include <unistd.h>

/*
 * fd:文件描述符
 * addr:数据首地址
 * count: 写入数据的字节个数
 * return:成功:写入字节个数 失败:-1
 */
ssize_t write(int fd, const void *addr, size_t count);

    read 函数

         指定数目的数据读到内存

#include <unistd.h>

/*
 * fd:文件描述符
 * addr:内存首地址
 * count:读取的字节个数
 * return:成功:读取的字节个数  失败:-1
 */
ssize_t read(int fd, void *addr, size_t count);

    remove 库函数

        删除文件

#include <stdio.h>

/*
 * pathname: 文件路径+文件名
 * return:成功:0 失败:-1
 */
int remove(const char * pathname);

 例子:

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

void test1();
void test2();
void test3();

int main(int argc, char *argv[])
{
    test1();
    test2();
    test3();
    return 0;
}

void test1()
{
    char buf[32] = "";

    read(0, buf, sizeof(buf));
    write(1, buf, strlen(buf));
}

void test2()
{
    int fd = 0;
    char str[] = "hello file";

    fd = open("./a.txt",O_WRONLY | O_CREAT | O_EXCL, 0777);
    
    if(-1 == fd)
    {
        perror("open");
        return;
    }

    printf("fd = %d\n", fd);
    write(fd, str, strlen(str));

    close(fd);
}

void test3()
{
    int fd = 0;
    char buf[128] = "";
    int len = 0;

    fd = open("a.txt",O_RDONLY);
    if(-1 == fd)
    {
        perror("open");
        return;
    }
    
    len = read(fd, buf, sizeof(buf));
    printf("len = %d\n", len);
    printf("buf = %s\n", buf);
    
    close(fd);
}

 打印:

 

系统调用与库函数

      不需要调用系统调用

              不需切换到内核空间 即可完成函数全部功能, 并且将结果反馈给应用程序

              如 :strcpy、 bzero 等字符串操作函数

     需要调用系统调用

              需要切换到内核空间, 这类函数通过封装系统调用去实现相应功能

              如 printf、 fread 等

    库函数与系统调用的关系

           并不是所有的系统调用都被封装成了库函数, 系统提供的很多功能都必须通过系统调用才能实现

        系统调用是需要时间, 程序中 频繁使用 系统调用会 降低程序的运行效率

       当运行内核代码时, CPU 工作在内核态, 在系统调用发生前需要保存用户态的栈和内存环境, 然后转入内核
态工作

       系统调用结束后,又要换回用户态切。 这种环境的切换会消耗掉许多时间

       库函数访问文件的时候根据需要,设置不同类型的缓冲区, 从而减少了直接调用 IO 系统调用的次数, 提高了访问
效率

        应用程序调用 printf 函数时, 函数执行的过程

     实现 cp 命令

          目标:
                使用系统调用实现 cp 命令

         原理:
              用系统调用 open 打开文件, 使用 read 从文件读数据, 使用 write 向文件写数据
              传给可执行程序的参数个数存放在 main 函数的 argc 中, 参数首地址存放在指针数组 argv 中

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

int main(int argc, char const * argv[])
{
    int srcFd = 0;
    char fileName[64] = "";
    int dstFd = 0;
    char buf[128] = "";
    int len = 0;

    if(3 != argc)
    {
        printf("请按 ./a.out srcdst 传参\n");
        return 0;
    }

    // 打开源文件
    srcFd = open(argv[1], O_RDONLY);
    if(-1 == srcFd)
    {
        perror("open");
        return 0;
    }

    // 打开目的文件
    sprintf(fileName, "%s/%s",argv[2],argv[1]);
    dstFd = open(fileName, O_WRONLY | O_CREAT, 0777);
    if(-1 == dstFd)
    {
        perror("open");
        return 0;
    }

    while(1)
    {
        len = read(srcFd, buf, sizeof(buf));
        if(len <= 0)
        {
            break;
        }
        write(dstFd, buf, len);
    }

    close(srcFd);
    close(dstFd);
    return 0;
}