• 秋招准备--基础知识复习--系统编程


    Lab1 进程

    定义
    进程的定义是操作系统中资源分配的基本单位,是程序的执行实体。具体地来分析这个定义的一体两面:

    1. 在资源分配方面,每个进程都有虚拟的进程空间,其中包括代码区,全局静态区,BSS区,堆区,文件映射区,栈区。
    2. 在执行方面,进程有多个状态,如运行态,阻塞态,就续态等,需要操作系统进行进程调度,来决定进程什么时候占用CPU执行。

    特别进程

    1. 僵尸进程:子进程已结束,但父进程可能因为在死循环中,没有回收其资源
    2. 孤儿进程:父进程已结束,子进程还存在,此时子进程的父进程成为init的PID为1的init进程,在子进程结束后,由init进程进行资源回收。
    3. 守护进程:参考链接独立于其他进程,不受终端控制,在后台运行,如init、systemd等,需要fork2次并关闭fork的父进程才能创建1个守护进程,第1次是为了使进程摆脱原来进程组的控制,fork后关闭父进程,则子进程不是进程组长,此时可调用setsid()创建新会话,以自成进程组,自成会话,避免和其他进程组和会话产生联系;第二次fork并关闭父进程是因为子进程为进程组组长,可开启1个终端,将其关闭后,孙进程则不再是进程组组长,无法开启终端,保证此守护进程不受终端控制。

    其他

    资源回收表示将子进程的PCB给释放掉,不再占用内存,因为子进程已经无法被调度或使用。可使用wait来阻塞地回收子进程资源,或使用waitpid来等待特定子进程结束。使用WIFEXITED(&status)来判断进程是否正常退出,WEXITSTATUS(&status)来获取进程退出状态。
    子进程
    子进程和父进程的资源是独立的,如全局变量等,一旦子进程fork()出来之后,对变量的操作就不会再对父进程产生影响,同理,fork()出来之后,父进程对变量的操作也不会对子进程产生影响。

    lab2 进程间通信

    参考链接

    PIPE 管道

    PIPE是半双工通信,即数据只能向一个方向传输,一端读一端写,创建管道其实就是1个长度为2的数组fd[2],fd[0]表示读端(一般用0表示没贡献内容来记忆),fd[1]表示写端。
    PIPE可用于父子进程通信,父进程创建管道后fork出1个子进程,然后父进程关闭写端,子进程关闭读端,则可以在此管道中进行父子进程的通信了。
    PIPE的升级版是FIFO,即命名管道,可用于2个非继承关系的进程之间的通信。

    消息队列

    参考链接

    SOCKET

    socket一般是用在不同主机之间的通信,如TCP、UDP通信;也可用于本机的进程间通信,如dbus。
    创建1个socket进行进程间通信,一般有1个客户端和1个服务端

    服务端

    • 创建套接字

      int socket(int domain, int type, int protocol)
      
      • 1
      • domain表示协议域,如使用AF_INET表示IPv4,AF_INET6表示IPv6。
      • type表示套接字类型,SOCK_STREAM表示流式套接字,提供可靠的,面向连接的,基于字节流的通信,确保数据不丢失不重复不乱序不出错,可用于TCP;SOCK_DGRAM表示数据报,提供不可靠,无连接的通信,通过分组传输数据。
      • protoccol指定使用的协议,如IPROTO_TCP表示TCP协议,IPROTO_UDP表示UDP协议,若为0则系统会根据domaintype自设定使用协议。
    • 设置套接字选项

      int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen)
      
      • 1
      • level表示指定选项的级别或协议,可以这么理解,socket本身有非常多的属性,如SO_REUSEPORT表示是否允许多个套接字绑定同一个端口,TCP_KEEPCNT表示发送多少次Keep-Alive报文后仍未收到响应则认定连接断开等等。而level可以设置一整组属性的值,如level = SOL_SOCKET 表示通用套接字,level = IPPROTO_TCP 表示控制TCP套接字行为等
      • optname表示要设置的属性,optval表示要将属性设为什么目标值,optlen表示目标属性值的长度。如以下表示将SO_REUSEPORT属性设为1,则允许多个套接字绑定到同一个端口,可用于多进程服务器。
        int opt = 1;
        setsockopt(server_fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt))
        
        • 1
        • 2
    • 设置套接字地址
      以下结构体用来存储套接字的地址信息,主要需要设置3个字段

      struct sockaddr_in address;
      
      • 1
      • sin_family:协议族,AF_INET表示IPv4,AF_INET6表示IPv6
      • address.sin_addr.s_addr:可以接收什么IP地址的连接,若可以接收来自任意地址的连接,则可设为INADDR_ANY
      • address.sin_port :套接字监听的端口号
    • 绑定套接字
      将套接字和特定的网络地址和端口号绑定,以便套接字能接受来自指定地址的连接。

      int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
      
      • 1
    • 监听连接
      backlog表示等待连接队列的最长长度,如果请求连接的客户端数量超过此值,则连接请求会被拒绝,若小于此值,则将请求放在连接队列里,直到服务器有能力处理该连接。

      int listen(int sockfd, int backlog);
      
      • 1
    • 接受客户连接请求
      accept函数接受一个客户连接,并创建1个新的套接字用于和客户通信,返回值则是新创建的套接字

      int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
      
      
      • 1
      • 2
    • 开始通信

      • ssize_t read(int fd, void *buf, size_t count);读取客户端请求

      • ssize_t send(int sockfd, const void *buf, size_t len, int flags); 回复客户端请求,flags用于设置一些属性,修改send函数的行为。

    • 服务端完整代码

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main() {
        int server_fd, new_socket;
        struct sockaddr_in address;
        int opt = 1;
        int addrlen = sizeof(address);
        char buffer[1024] = {0};
        const char* hello = "Hello from server";
    
        // 创建套接字
        if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
            perror("Socket creation failed");
            return -1;
        }
    
        // 设置套接字选项
        if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
            perror("Setsockopt failed");
            return -1;
        }
    
        address.sin_family = AF_INET;
        address.sin_addr.s_addr = INADDR_ANY;
        address.sin_port = htons(8080);
    
        // 绑定套接字
        if (bind(server_fd, (struct sockaddr *)&address, sizeof(address))<0) {
            perror("Bind failed");
            return -1;
        }
    
        // 监听连接
        if (listen(server_fd, 3) < 0) {
            perror("Listen failed");
            return -1;
        }
    
        std::cout << "Server listening..." << std::endl;
    
        if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen))<0) {
            perror("Accept failed");
            return -1;
        }
    
        int valread;
        while ((valread = read(new_socket, buffer, 1024)) > 0) {
            std::cout << "Client: " << buffer << std::endl;
            send(new_socket, hello, strlen(hello), 0);
        }
        std::cout << "Connection closed by client." << std::endl;
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    客户端

    • 客户端完整代码
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main() {
        int sock = 0;
        struct sockaddr_in server_addr;
        char buffer[1024] = {0};
        const char* hello = "Hello from client";
    
        // 创建套接字
        if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
            std::cerr << "Socket creation error" << std::endl;
            return -1;
        }
    
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(8080);
    
        // 将IPv4地址从文本转换为二进制
        if (inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr) <= 0) {
            std::cerr << "Invalid address/ Address not supported" << std::endl;
            return -1;
        }
    
        // 连接到服务器
        if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
            std::cerr << "Connection Failed" << std::endl;
            return -1;
        }
    
        send(sock, hello, strlen(hello), 0);
        std::cout << "Hello message sent" << std::endl;
    
        int valread;
        while ((valread = read(sock, buffer, 1024)) > 0) {
            std::cout << "Server: " << buffer << std::endl;
        }
        std::cout << "Connection closed by server." << std::endl;
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    Lab3 线程

    参考链接
    线程的定义是操作系统中进行调度的基本单位,它出现的原因就是来解决进程的不足,因此需要了解它相比进程的区别,一切区别都从定义出发:线程是调度基本单位,而进程是资源分配基本单位,同个进程中的线程的虚拟地址空间是共享的,因此可以节约资源分配的时间和空间。

    • 隔离性:进程有各自独立的虚拟地址,线程只有栈/寄存器/程序计数器/TCB等少数独立数据
    • 调度:参考链接线程切换更快
    • 通信:线程通信不用通过内核,可直接访问全局变量等共享数据

    C++线程编程

    thread:直接使用thread(fun, arg1, arg2…)创建线程并指定线程入口函数和对应参数
    join:在A线程中join(B线程),则是阻塞A线程,直到B线程终止后才继续执行A线程剩余部分
    detach:在A线程中detach(B线程),则是将B线程从A线程中分离,B线程终止后立即回收其资源
    unique_lock:使用unique_lock(mutex)保护1个临界区,并且在离开作用域时会自动解锁,和智能指针的思想很像,不需要用户手动解锁,符合RAII原则

  • 相关阅读:
    深入解析 Socks5 代理与网络安全
    SpringMVC基础:RestFul风格
    每天一点C++——C++中的强制类型转换
    苍穹外卖项目笔记(1)
    AKHQ Nomad 部署方案
    遥感图像分割 | 基于一种类似UNet的Transformer算法实现遥感城市场景图像的语义分割_适用于卫星图像+航空图像+无人机图像
    Monocle 3 | 太牛了!单细胞必学R包!~(二)(寻找marker及注释细胞)
    类和对象(末)
    PAT 乙级 1101 B是A的多少倍
    vs2019+Qt 使用 Qlabel 在界面上显示图像及显示失真问题
  • 原文地址:https://blog.csdn.net/m0_51531927/article/details/133252281