UC编程学习

UC编程学习

环境变量

什么是环境变量

  1. bash用一个叫做环境变量的特性来存储有关工作环境的信息
  2. 进程可以通过环境变量访/问计算机的资源
  3. 在终端下输入env命令,可以查看环境变量列表
  4. 通过echo $name 可以查看某个环境变量的值(将name替换即为要查的值)

环境变量的添加

  1. 在终端窗口中输入 键=值 形式的内容,回车
  2. 比如 FOOD=guobaorou,表示在当前bash中,添加名为 FOOD,
    值为 guobaorou的环境变量。
  3. 如果环境变量FOOD存在,则更改其值。
  4. 强调,在添加环境变量时,登号左右两侧不要添加空格

常见环境变量

  1. PATH环境变量,通过 echo $PATH命令查看
    该环境变量所记录的是bash进程对命令的检索路径
  2. 将当前目录添加到PATH中
    PATH=&PATH:.
    注意!!
    对环境变量的设貴仅对当前shell进程有效,开启新的终端,之前的操作不会被保留。
    如果要环境变量的设置对每个bash进程都有效,修改.bashrc文件,执行 source ~/.bashrc 命令,可以使文件立即对当前bash生3效。

环境变量表

每个进程都有一张独立的环境变量表,其中的每个条目都是一个形如“键=值“形式的环境变量
所谓环境变量表就是一个以NULL指针结束的字符指针数组,其中的每个元素都是一个字符指针,指向一个以空字符结尾的宇符串,该字符串就是形如〞键=值〞形式的环境变量。该指针数组的地址保存在全局变量environ中
在这里插入图片描述

代码实现

//环境变量的获取

#include<stdio.h>

int main(int argc,char* argv[],char* envp[]){
    //第一种方式
    extern char** environ;
    /*for(char** pp = environ;*pp;pp++){
        printf("%s\n",*pp);
    }*/
    for(char** pp = envp;*pp;pp++){
        printf("%s\n",*pp);
    }
    printf("---------------\n");
    printf("environ = %p\n",environ);
    printf("envp    = %p\n",envp);
    return 0;
}

以下为部分打印结果
在这里插入图片描述

错误处理

errno

#include<errno.h>

  • 头文件/usr/include/errno.h中包含了对errno全局变量的外部声明
  • 在头文件/usr/include/asm-generic/errno-bashe.h中包含各种错误号的宏定义

strerror()

#include <string.n>
char* strerror(int errnum)
功能:将整数形式的错误号转换为有意义的字符串
参数:errnum,是errno全局变量
错误号
返回值:返回与参数错误号对应的描述字符串

perror()

#include <stdio.h>
void perror(char const* tag)
功能:在标准出错设备上打印最近一次函数调用的错误信息
参数:tag 为用户自己制定的提示内容,输出时,会自动在该提示内容和错
误信息之间添加冒号进行分隔

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>// errno全局变量
#include<string.h>//strerror()
int main(void){
    char* p = malloc(0xffffffffffffffff);
    if(p == NULL){
        printf("errno = %d\n",errno);
        printf("malloc函数出错了%s\n",strerror(errno));
        perror("malloc");
        return -1;
    }
    free(p);
    p = NULL;
    return 0;
}

输出如下
在这里插入图片描述

静态库制作使用

定义

静态库的本质就是将多个目标文件打包成一个文件
链接静态库的过程就是将库中被调用的代码复制到调用模块中
静态库的拓展名是.a 例:libxxx.a
在这里插入图片描述

ar命令

ar [选项] <静态库文件><目标文件列表>

  • -r 将目标插入到静态库中,已存在则更新
  • -q 将目标文件追加到静态库尾
  • -d 从静态库中删除目标文件
  • -t 列表显示静态库中的目标文件
  • -x 将静态库展开为目标文件
    以构建数学库为例,静态库的构建顺序如下:
    -1、编辑库的实现代码和接口声明:
    计算模块:calc.h、calc.c
    显示模块:show.h、show.c
    接口文件:math.h
    2、编译成目标文件
    gcc -c calc.c
    gcc -c show.c
    3、打包成静态库
    ar -r libmath.a calc.o show.o
    4、使用
    若静态库和代码在同一目录下
    gcc main.c libmath.a
  • 若不在同一目录下,假设在上级目录
    用-指定库名,用-L指定库路径
    gcc mian.c -lmath -L…
    用-l指定库名,用LIBRARY_PATH环不境变量指定库路径
    export LIBRARY PATH=$LIBRARY PATH:
    gcc main.c -Imath

动态库

定义

  • 动态库和静态库不同,链接动态库不需要将被调用的函数代码复制到包含
  • 调用代码的可执行文件中,相反链接器会在调用语句处嵌入一段指令,该程序执行到这段指令时,会加载该动态库并寻找被调用函数的入口地址并执行之。
  • 如果动态库中的代码同时为多个进程所用,动态库在内存的实例仅需一份,为所有使用该库的进程所共享,因此动态库亦称共享库。
  • 动态库的拓展名是.so 例ibxxx.so
    在这里插入图片描述

动态库的制作使用

假设文件还是静态库之前编写的文件
gcc -shared -fpic calc.c show.c -o libmath.so
直接使用
gcc main.c libmath.so -o math
若不在同一目录下,假设在上级目录
用-指定库名,用-L指定库路径
gcc mian.c -lmath -L… -o math
用-l指定库名,用LIBRARY_PATH环不境变量指定库路径
export LIBRARY_PATH=$LIBRARY_PATH:
gcc main.c -Imath
注意:
运行时需要保证LD_LIBRARY_PATH环境变量中包含共享库所在的路径用
以告知链接器在运行时链接动态库。

  • export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:
    在可执行程序的链接阶段,并不将所调用函数的二进制代码复制到可执行程序中,而只是将该函数在共享库中的地址嵌入到调用模块中,因此运行时需要依赖共享库。

动态库的动态加载

在程序执行的过程中,开发人员可以动态加载共享库(什么时候用什么时候加载),这样可以提高内存的利用效率。

使用

使用这组函数需要包含此头文件,并链接该库
#include <dlfcn.h>

dlopen()

void dlopen(char const filename, int flag);

  • 功能:将共享库载入内存并获得其访问句柄
    参数:
  • filename
    动态库路径,若只给文件名不带目录,则根据LD_LIBRARY_PATH环境变量的值搜索动态库
  • flag
    加载方式,可取以下值:
    RTLD_LAZY -延迟加载,使用动态库中的符号时才真的加载进内存。
    RTLD_NOW - 立即加载。
  • 返回值:成功返回动态库的访问句柄,失败返回NULL。
    句柄:句柄唯一地标识了系统内核所维护的共享库对象,将作为后续函数调用的参数
dlsym()

void* dIsym(void* handle, char const* symbol);

  • 功能:从已被加载的动态库中获取特定名称的符号地址
    参数:
    handle 动态库访问句柄
    symbol 符号名
    返回值:成功返回给定符号的地址,失败返回NULL。
    该函数所返回的指针为void*类型,需要造型为与实际目标类型相一致的指针,才能使用。
dlclose()

int dlclose(void* handle);
功能:从内存中卸载动态库
参数:handle 动态库句柄
返回值:返回值:成功返回0,失败返回非0。
所卸载的共享库未必会真的从内存中立即消失,因为其他程序可能还需要使用该库
只有所有使用该库的程序都显示或隐式地卸载了该库,该库所占用的内存空间才会真正得到释放
无论所卸载的共享库是否真正被释放,传递给dlclose两数的句柄都会在该函数成功返回后立即失效

dlerror()

功能:获取在加载、使用和卸载共享库过程中所发生的错误
返回值:有错误则返回指向错误信息字符串的指针,否则返回NULL。

代码示例
//动态库的动态加载
#include<stdio.h>
#include<dlfcn.h>

int main(void){
    //将动态库载入内存
    void* handle = dlopen("/home/tarena/2212/day02/shared/libmath.so",RTLD_NOW);
    if(handle == NULL){//dlopen失败返回NULL 
        fprintf(stderr,"dlpen:%s\n",dlerror());
        return -1;
    }
    //获取库中函数的地址
    int (*add)(int,int) = dlsym(handle,"add");
    if(add == NULL){
        fprintf(stderr,"dlsym:%s\n",dlerror());
        return -1;
    }
    int (*sub)(int,int) = dlsym(handle,"sub");
    if(sub == NULL){
        fprintf(stderr,"dlsym:%s\n",dlerror());
        return -1;
    }
    void (*show)(int,char,int,int) = dlsym(handle,"show");
    if(show == NULL){
        fprintf(stderr,"dlsym:%s\n",dlerror());
        return -1;
    }
    //使用
    int a = 123,b = 456;
    show(a,'+',b,add(a,b));
    show(a,'-',b,sub(a,b));
    //卸载动态库
    //int ret = dlclose(handle);
    //if(ret){
    if(dlclose(handle)){
        fprintf(stderr,"dlclose:%s\n",dlerror());
        return -1;
    }
    return 0;
}

辅助工具

在这里插入图片描述

虚拟地址

虚拟地址空间

32位操作系统

  • 对于32位操作系统而言,每个进程所得到的虚拟地址都在一个固定的范围内,不会超出这个范围,我们把这个范围称为虚拟地址空间。
  • 所谓的虛拟地址空向本质就是一个地址范国,表示程序的寻址能力。我们所看到的虚拟地址,都是在这个范围内。
  • 对于32位操作系统而言,其虚拟地址空间范围0x00000000到0xFFFFFFFF
    其中,0——3G-1的范围归用户所使用,称为用户地址空间3G——4G-1的范围归内核使用,称为内核地址空间。

64位操作系统

  • 对于64位的操作系统而言,目前应用程序没有那么大的内存需求,所以不支持完全的64位虚拟地址。
  • 64位系统上,其用户地址空问范围是:0x0000 0000 0000 0000 ~ 0x0000 FFFF FFFF FFFF
  • 64位系统上,其内核地址空间范围是:0xFFFF 0000 0000 0000~ 0xFFFF FFFF FFFF FFFF
    内核地址空间和用户地址空间之间是不规范地址空间,不允许使用。
    用户地址空间的代码不能直接访问内核空间的代码和数据,但可以通过系统调用进入内核态,间接与系统内核交互

虚拟地址空间布局(32位操作系统为例)

用户地址空间中从低地址到高地址(由下往上看)布局如图:
在这里插入图片描述
代码验证

//虚拟地址空间布局
#include<stdio.h>
#include<stdlib.h>
const int const_global = 1;//常全局变量
int init_global = 2;//初始化全局变量
int uninit_global;//未初始化全局变量

int main(int argc,char* argv[],char* envp[]){
    static const int const_static = 3;//常静态变量
    static int init_static = 4;//初始化静态变量
    static int uninit_static;//未初始化静态变量
    const int const_local = 5;//常局部变量
    int local;//局部变量
    char* string = "hello";//字面值常量
    int* heap = malloc(sizeof(int));//堆变量
    printf("----------参数和环境---------\n");
    printf("          环境变量:%p\n",envp);
    printf("        命令行参数:%p\n",argv);
    printf("-------------栈区------------\n");
    printf("        常局部变量:%p\n",&const_local);
    printf("          局部变量:%p\n",&local);
    printf("-------------堆区------------\n");
    printf("            堆变量:%p\n",heap);
    printf("-------------BSS区-----------\n");
    printf("  未初始化全局变量:%p\n",&uninit_global);
    printf("  未初始化静态变量:%p\n",&uninit_static);
    printf("-------------数据区----------\n");
    printf("    初始化静态变量:%p\n",&init_static);
    printf("    初始化全局变量:%p\n",&init_global);
    printf("-------------代码区----------\n");
    printf("        字面值常量:%p\n",string);
    printf("        常静态变量:%p\n",&const_static);
    printf("        常全局变量:%p\n",&const_global);
    printf("              函数:%p\n",main);
    printf("-----------------------------\n");

    return 0;
}

在这里插入图片描述

内存映射的建立与解除

没有与物理地址建立起映射关系的虚拟地址,无法直接使用,我们可以通过系统调用mmap函数手动建立虚拟地址和物理地址之间的映射关系。

mmap()

需要包含此头文件
#include<sys/mman.h>
– void* mmap(void* start, size_t length, int prot, int flags, int fd, off_t offset);

  • 功能:建立虚拟内存到物理内存或磁盘文件的映射:
  • 参数:
  • start:映射区虚拟内存的起始地址,NULL系统自动选定后返回。
  • length:映射区字节数,自动按页圆整。一页的大小是基于磁盘的读写单位是 block(一般大小为 4KB),而基于内存的读写单位是地址(虽然内存的管理与分配单位是 4KB)
  • prot:
    映射区操作权限,可取以下值:
    PROT_READ- 映射区可读
    PROT_WRITE- 映射区可写
    PROT_EXEC-映射区可执行
    PROT_NONE- 映射区不可访问
  • flags:映射标志,可取以下值:
    MAP_ANONYMOUS- 匿名映射,将虚拟内存映射到物理内存而非文件,
    忽略fd和offset参数
    MAP_PRIVATE -对映射区的写操作只反映到缓冲区中并不会真正写入文件
    MAP_ SHARED-对映射区的写操作直接反映到文件中
    MAP_DENYWRITE - 拒绝其它对文件的写操作
    MAP_FIXED- 若在start上无法创建映射,则失败(无此标志系统会自动调整)
  • fd:
    文件描述符
  • offset:文件偏移量,自动按页(4K)对齐
  • 返回值:成功返回映射区虛拟内存的起始地址,失败返回MAP_FAILED(-1)

munmap()

需要包含此头文件
#include<sys/mman.h>
int munmap(void* start, size t length);
参数:

  • start:
    映射区虚拟内存的起始地址。
  • length:映射区字节数,自动按页圆整。
  • 返回值:成功返回0,失败返回-1。
    munmap允许对映射区的一部分解映射,但必须按页处理

代码实现

//内存映射
#include<stdio.h>
#include<string.h>
#include<sys/mman.h>// mmap  munmap

int main(void){
    //建立映射
    char* start = mmap(NULL,8192,PROT_READ | PROT_WRITE,MAP_PRIVATE | MAP_ANONYMOUS,0,0);
    if(start == MAP_FAILED){
        perror("mmap");
        return -1;
    }
    //向内存中写入数据
    strcpy(start,"铁锅炖大鹅");
    printf("%s\n",start);

    //解除映射
    if(munmap(start,4096) == -1){
        perror("munmap");
        return -1;
    }
    //printf("%s\n",start);
    
    char* start2 = start + 4096;
    strcpy(start2,"小鸡炖蘑菇");
    printf("%s\n",start2);
    if(munmap(start2,4096) == -1){
        perror("munmap");
        return -1;
    }
    printf("%s\n",start2);
    return 0;
}

文件打开与关闭

open()

头文件
#include <fcntl.h>
int open(char const* pathname, int flags, mode t mode);
功能:打开已有的文件或创建新文件
参数:

  • pathname 文件路径
  • flags 状态标志,可以以下取值
    O_RDONLY - 只读
    O_WRONLY - 只写
    O_RDWR -读写
    O_APPEND - 追加
    O_CREAT - 不存在即创建,已存在即打开
    O_ExCL - 不存在即创建,已存在即报错
    O_TRUNC - 不存在即创建,已存在即清空
  • mode 权限模式
    仅在创建新文件时有效,可用形如0xxx的三位八进制数表示,由高
    位到低位依次表示拥有者用户、同组用户和其它用户的读、写和执行权限,读权限用4表示,写权限用2表示,执行权限用1表示,最后通过加法将不同的权限组合在一起
    •返回值:所返回的一定是当前未被使用的,最小文件描述符
  • 注意!!!
    调用进程的权限掩码会屏蔽掉创建文件时指定的权限位,如创建文件时指定杈限0666,进程权限掩码0002,所创建文件的实际权限为:0666&~0002=0664

close()

头文件
#include <unistd.h>

  • int close(int fd);
    – 功能:关闭处于打开状态的文件描述符
    – 参数:fd 处于打开状态的文件描述符,即上面open的返回值
    – 返回值:成功返回0,失败返回-1

代码实现

//文件的打开和关闭
#include<stdio.h>
#include<fcntl.h> // open
#include<unistd.h> // close

int main(void){
    int fd = open("./open.txt",O_RDWR | O_CREAT | O_TRUNC,0777);
    if(fd == -1){
        perror("open");
        return -1;
    }
    printf("fd = %d\n",fd);

    close(fd);
    return 0;
}

文件内核结构

  • 一个处于打开状态的文件,系统会为其在内核中维护一套专门的数据结构,保存该支件的信息,直到关闭
    – v节点与v节点表
    – 文件的元数据和在磁盘上的存储位置都保存在其节点中,而节点保存在分区柱面组的i节点表中,在打开文件时将其节点信息读入内存,并辅以其它的必要信息形成一个专门的数据结构,势必会提高对该文件的访问效率,这个存在于进程的内核空间,包含文件i节点信息的数据结构被称为v节点。多个v节点结构以链表的形式构成v节点表
    – 文件表项与文件表
    – 由文件状态标志(来自open函数的flags参数)、文件读写位置(最后一次读写的最后一个字节的下一个位置)和v节点指针等信息组成的内核数据结构被称为文件表项。通过文件表项一方面可以实时记录每次读写操作的准确位置,另一方面可以通过v节点指针访问包括该文件各种元数据和磁盘位置在内的街点信息。多个文件表项以链表的形式构成文件表
  • 多次打开同一个文件,无论是在同一个进程中还是在不同的进程中,都
    只会在系统内核中产生一个v节点
  • 每次打开文件都会产生一个新的文件表项,各自维护各自的文件状态标
    志和当前文件偏移,却可能因为打开的是同一个文件而共享同一个v节点
  • 打开一个文件意味着内存资源(节点、文件表项等)的分配,而关闭文件其实就是为了释放这些资源,但如果所关闭的文件在其它进程中正处于打开状态,那么其v节点并不会被释放,直到系统中所有曾打开过该
    文件的进程都显式或隐式地将其关闭,其v节点才会真正被释放
  • 一个处于打开状态的文件也可以被删除,但它所占用的磁盘空间直到它
    的v节点彻底消失以后才会被标记为自由
    内核结构如图所示
    在这里插入图片描述

文件描述符

  • 由文件的内核结构可知,一个被打开的文件在系统内核中通过文件表项和v节点加以标识。有关该文件的所有后续操作,如读取、写入、随机访问,乃至关闭等,都无一例外地要依赖于文件表项和v节点。因此有必要将文件表项和V节点体现在完成这些后续操作的函数的参数中。但这又势必会将位于内核空间中的内存地址暴露给运行于用户空间中的程序代码。一旦某个用户进程出现操作失误,极有可能造成系统内核失稳,进而影响其它正常运行的用户进程。这将对操作系统的安全运行造成极大的威胁
  • 为了解决内核对象在可访问性与安全性之问的矛盾,Unix系统通过所谓的文件描述符,将位于内核空间中的文件表项间接地提供给运行于用户空间中的程序代码
  • 为了便于管理在系统中运行的各个进程,内核会维护一张存有各进程信息的列表,谓之进程表。系统中的每个进程在进程表中都占有一个表项。每个进程表项都包含了针对特定进程的描述信息,如进程ID、用户1D、组ID等,其中也包含一个被称为文件描述符表的数据结构
  • 文件描述符表的每个表项都至少包含两个数据项文件描述符标志和文件表项指针,而所谓文件描述符,其实就是文件描述符表项在文件描述符表中从0开
    始的下标
    在这里插入图片描述
    在这里插入图片描述
    系统内核缺省为每个进程打开三个文件描述符,它们在unistd.h头文件中
    被定义为三个宏
    #define STDIN FILENO O // 标准输入
    #define STDOUT FILENO 1 // 标准输出
    #define STDERR_FILENO 2 //标准错误

文件读写

write()

#include <unistd.h>
ssize_t write(int fd, void const* buf, size_t count);
功能:向指定的文件写入数据
参数:

  • fd文件描述符
  • buf 内存缓冲区,即要写入的数据
  • count 期望写入的字节数
  • 返回值:成功返回实际写入的字节数,失败返回-1

代码示例

//向文件中写入数据
#include<stdio.h>
#include<string.h>
#include<fcntl.h>//open
#include<unistd.h>// close write

int main(void){
    //打开shared.txt文件 
    int fd = open("./shared.txt",O_CREAT | O_TRUNC | O_WRONLY,0664);
    if(fd == -1){
        perror("open");
        return -1;
    }
    //向文件中写入数据  
    char* buf = "铁锅炖大鹅";
    ssize_t size =  write(fd,buf,strlen(buf));
    if(size == -1){
        perror("write");
        return -1;
    }
    printf("实际向文件中写入%ld个字节\n",size);
    //关闭文件
    close(fd);
    return 0;
}

read()

#include <unistd.h>
ssize_t read(int fd, void* buf, size_t count);
功能:从指定的文件中读取数据
参数:

  • fd 文件描述符
  • buf 内存缓冲区,存读取到的数据
  • count 期望读取的字节数
  • 返回值:成功返回实际读取的字节数,失败返回-1

代码示例

//读取文件
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>

int main(void){
    //打开文件shared.txt
    int fd = open("./shared.txt",O_RDONLY);
    if(fd == -1){
        perror("open");
        return -1;
    }
    //读取文件
    char buf[32] = {};
    ssize_t size = read(fd,buf,sizeof(buf)-1);
    if(size == -1){
        perror("read");
        return -1;
    }
    printf("%s\n",buf);
    printf("实际读取到%ld个字节\n",size);
    //关闭文件
    close(fd);
    return 0;
}

修改文件读写位置Iseek()

#include <unistd.h>
off_t Iseek(int fd, off_t offset, int whence);
功能:人为调整文件读写位置
参数:
fd:文件描述符。
offset:文件读写位置偏移字节数(可以负数)
whence: offset参数的偏移起点,
可以如下取值:

  • SEEK_SET -从文件头(首字节)开始
  • SEEK_CUR-从当前位置(最后被读写字节的下一个字节)开始
  • SEEK_END- 从文件尾(最后一个字节的下一个字节)开始
  • 返回值:成功返回调整后的文件读写位置,失败返回-1

lseek函数的功能仅仅是修改保存在文件表项中的文件读写位置,并不实际引发任何V/0动作
lseek (fd, -7, SEEK CUR);11/ 从当前位置向文件头偏移7字节
Iseek (fd, O, SEEK CUR); // 返回当前文件读写位置
seek (fd, 0, SEEK END);1/返回文件总字节数

代码实现

#include<stdio.h>
#include<string.h>
#include<unistd.h>// lseek  write  read close
#include<fcntl.h>// open

int main(void){
    //打开文件
    int fd = open("./lseek.txt",O_WRONLY | O_CREAT | O_TRUNC,0664);
    if(fd == -1){
        perror("open");
        return -1;
    }
    //写入数据 hello world!
    char* buf = "hello world!";
    if(write(fd,buf,strlen(buf)) == -1){
        perror("write");
        return -1;
    }
    off_t len = lseek(fd,0,SEEK_CUR);
    printf("此时的读写位置是%ld\n",len);
    //调整文件读写位置
    if(lseek(fd,-6,SEEK_END) == -1){
        perror("lseek");
        return -1;
    }
    //再次写入数据
    buf = "linux!";
    if(write(fd,buf,strlen(buf)) == -1){
        perror("write");
        return -1;
    }
    //再次调整文件读写位置
    if(lseek(fd,8,SEEK_END) == -1){
        perror("lseek");
        return -1;
    }
    //第三次写入
    buf = "感冒真难受";
    if(write(fd,buf,strlen(buf)) == -1){
        perror("write");
        return -1;
    }  
    
    
    //关闭文件
    close(fd);
    return 0;
}




文件描述符的复制

#include <unistd.h>

dup()

int dup(int oldfd);

  • 功能:复制文件描述符表的特定条目到最小可用项:
  • 参数:oldfd:源文件描述符
  • 返回值:成功返回目标文件描述符,失败返回-1
    dup函数将oldfd参数所对应的文件描述符表项复制到文件描述符表第一个空闲项中,同时返回该表项对应的文件描述符。dup西数返回的文件描述符一定是调用进程当前末使用的最小文件描述符
    在这里插入图片描述
  • 注意,当关闭文件时,即使是由dup函数产生的文件描述符副本,也应该通过close函数关闭,因为只有当关联于一个文件表项的所有文件描述符都被关闭了,该文件表项才会被销毁,类似地,也只有当关联于一个v节点的所有文件表项都被销毀了,v节点才会被从内存中州除,因此从资源合理利用的角度讲,凡是明确不再继续使用的文件描述符,都应该尽可能及时地用close函数关闭
  • dup函数究竟会把oldfd参数所对应的文件描述符表项,复制到文件描述符表的什么位置,程序员是无法控制的,这完全由调用该函数时文件描述符表的使用情况决定,因此对该西数的返回值做任何约束性假设都是不严谨的
  • 由dup函数返回的文件描述符与作为参数传递给该函数的文件描述符识的是同一个文件表项,而文件读写位置是保存在文件表项而非文件描述符表项中的,因此通过这些文件描述符中的任何一个,对文件进行读写或随机访问,都会影响通过其亡文件描述符操作的文件读写位置。这与多次通过open函数打开同一个文件不同
    在这里插入图片描述

dup2()

#include <unistd.h>
int dup2(int oldfd, int newfd);
功能:复制文件描述符表的特定条目到指定项:
参数:

  • aldfd:源文件描达符
  • newfd:目标文件描述符
  • 返回值:成功返回目标文件描述符(newfd),失败返回-1。

dup2两数在复制由oldfd参数所标识的源文件描述符表项时,会先检查由newfd参数所标识的目标文件描述符表项是否空闲,若空闲则直接将前者复制给后者,否则会先将目标文件描述符newfd关闭,使之成为空闲项,再行复制

//文件描述符的复制
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>

int main(void){
    //打开文件,得到文件描述符 oldfd
    int oldfd = open("./dup.txt",O_WRONLY | O_CREAT | O_TRUNC,0644);
    if(oldfd == -1){
        perror("open");
        return -1;
    }
    printf("oldfd = %d\n",oldfd);
    //复制文件描述符oldfd 得到新文件描述符 newfd
    //int newfd = dup(oldfd);
    int newfd = dup2(oldfd,STDOUT_FILENO);//STDOUT_FILENO即为1
    if(newfd == -1){
        perror("dup");
        return -1;
    }
    printf("newfd = %d\n",newfd);
    //通过oldfd向文件中写入数据hello world!
    char* buf = "hello world!";
    if(write(oldfd,buf,strlen(buf)) == -1){
        perror("write");
        return -1;
    }
    //通过newfd调整文件的读写位置
    if(lseek(newfd,-6,SEEK_END) == -1){
        perror("lseek");
        return -1;
    }
    //通过oldfd再次向文件中写入数据linux!
    buf = "linux!";
    if(write(oldfd,buf,strlen(buf)) == -1){
        perror("write");
        return -1;
    }
    //关闭文件,两个文件描述符都关闭,文件表象才被释放
    close(newfd);
    close(oldfd);
    return 0;
}

输出为

oldfd = 3

并不是oldfd = 3 newfd = 1

解释代码

printf
它免除了你的fopen-fwrite-fclose这个序列的调用,因为它直接将格式化的内容写入UNIX进程自然打开的1号文件描述符,即标准输出
dup2两数在复制由oldfd参数所标识的源文件描述符表项时,会先检查由newfd参数所标识的目标文件描述符表项是否空闲,若空闲则直接将前者复制给后者,否则会先将目标文件描述符newfd关闭,使之成为空闲项,再行复制

int newfd = dup2(oldfd,STDOUT_FILENO);//STDOUT_FILENO即为1

这段代码会将1号文件描述符即标准输出 关闭,并将”./dup.txt"文件复制给标准输出,此时1号文件描述符变为”./dup.txt",所以后面的printf(“newfd = %d\n”,newfd);

访问测试

access()

#include <unistd.h>

  • int access(char const* pathname, int mode);
    判断当前进程是否有访问某文件的权限
    参数:
  • pathname 文件路径
  • mode
    R_OK - 可读否
    W_OK - 可写否
    X_OK - 可执行否
    F_OK - 存在否
  • 返回值:成功返回0,失败返回-1
//访问测试
#include<stdio.h>
#include<unistd.h>

int main(int argc,char* argv[]){
    // ./a.out  hello.txt
    // 文件:hello.txt不存在
    // 文件:hello.txt可读,可写,不可执行
    if(argc < 2){
        fprintf(stderr,"用法:%s <文件名>\n",argv[0]);
        return -1;
    }
    
    printf("文件:%s",argv[1]);
    if(access(argv[1],F_OK) == -1){
        printf("不存在\n");
    }else{
        //能读否
        if(access(argv[1],R_OK) == -1){
            printf("不可读,");
        }else{
            printf("可读,");
        }
        //能写否
        if(access(argv[1],W_OK) == -1){
            printf("不可写,");
        }else{
            printf("可写,");
        }
        //能执行否
        if(access(argv[1],X_OK) == -1){
            printf("不可执行\n");
        }else{
            printf("可执行\n");
        }
    }
    return 0;
}

修改文件大小

truncate()

#include <unistd.h>
int truncate(char const* path, off t length);
int truncate(int fd, off t length);
功能:修改指定文件的大小
参数:

  • path 文件路径
  • length: 文件大小
  • fd: 文件描述符
  • 返回值:成功返回0,失败返回-1。

该函数既可以把文件截短,也可以把文件加长,所有的变均发生在文件的尾部,新增加的部分用数字0填充

//修改文件大小
#include<stdio.h>
#include<string.h>
#include<fcntl.h>
#include<unistd.h>

int main(void){
    //打开文件
    int fd = open("./trunc.txt",O_WRONLY | O_CREAT | O_TRUNC,0664);
    if(fd == -1){
        perror("open");
        return -1;
    }
    //向文件中写入数据
    char* buf = "opqrst";
    if(write(fd,buf,strlen(buf)) == -1){
        perror("write");
        return -1;
    }
    //修改文件
    if(truncate("./trunc.txt",3) == -1){
        perror("truncate");
        return -1;
    }
    //再次修改文件
    if(ftruncate(fd,6) == -1){
        perror("ftruncate");
        return -1;
    }
    
    //关闭文件
    close(fd);
    return 0;
}

文件锁

#include <fcntl.h>
int fcntl(int fd, F_SETLK/F_SETLKW, struct flock* lock);
功能:加解锁
参数:F_SETLK 非阻塞模式加锁,F_SETLKW 阻塞模式加锁
lock 对文件要加的锁
-返回值:成功返回0,失败返回-1
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

文件锁的内核结构

在这里插入图片描述
如下图
在这里插入图片描述

代码区

//文件锁
#include<stdio.h>
#include<string.h>
#include<unistd.h>//open ,close
#include<fcntl.h>//fcntl
#include<errno.h>//errno
int main(int argc,char* argv[]){
    // ./a.out  hello
    //打开文件
    int fd = open("./shared.txt",O_WRONLY | O_CREAT | O_APPEND,0664);
    if(fd == -1){
        perror("open");
        return -1;
    }
    //阻塞加锁,
    struct flock lock;
    lock.l_type = F_WRLCK;//写锁
    lock.l_whence = SEEK_SET;
    lock.l_start = 0;
    lock.l_len = 0;//锁到文件尾
    lock.l_pid = -1;
    /*if(fcntl(fd,F_SETLKW,&lock) == -1){
        perror("fcntl");
        return -1;
    }*/
    //非阻塞方式加锁
    while(fcntl(fd,F_SETLK,&lock) == -1){
        if(errno == EACCES || errno == EAGAIN){
            printf("文件被锁定,干点别的区...\n");
            sleep(1);
        }else{
            perror("fcntl");
            return -1;
        }
    }
    //写入数据  argv[1]  -->  "hello"
    for(int i = 0;i < strlen(argv[1]);i++){
        if(write(fd,&argv[1][i],sizeof(argv[1][i])) == -1){
            perror("write");
            return -1;
        }
        sleep(1);
    }
    
    //解锁
    struct flock unlock;
    unlock.l_type = F_UNLCK;//解锁
    unlock.l_whence = SEEK_SET;
    unlock.l_start = 0;
    unlock.l_len = 0;//锁到文件尾
    unlock.l_pid = -1;
    if(fcntl(fd,F_SETLKW,&unlock) == -1){
        perror("fcntl");
        return -1;
    }

    //关闭文件
    close(fd);
    return 0;
}


文件锁元数据

在这里插入图片描述
在这里插入图片描述
stat函数族通过stat结构体,向调用者输出文件的元数据
在这里插入图片描述
stat结构的st mode成员表示文件的类型和权限,该成员在stat结构中被声明为mode t类型,其原始类型在32位系统中被定义为unsigned int,即32位无符号整数,但到目前为止,只有其中的低16位有意义
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

辅助分析文件类型的实用宏

在这里插入图片描述
参数要填st_mode
在这里插入图片描述

//获取文件的元数据
#include<stdio.h>
#include<string.h>
#include<sys/stat.h>
#include<time.h>

//转类型和权限
// hello.txt --> s ---> s.st_mode  -->  -rw-rw-r--
char* mtos(mode_t m){
    static char s[11];
    if(S_ISDIR(m)){
        strcpy(s,"d");
    }else
    if(S_ISSOCK(m)){
        strcpy(s,"s");
    }else
    if(S_ISCHR(m)){
        strcpy(s,"c");
    }else
    if(S_ISBLK(m)){
        strcpy(s,"b");
    }else
    if(S_ISLNK(m)){
        strcpy(s,"l");
    }else
    if(S_ISFIFO(m)){
        strcpy(s,"p");
    }else{
        strcpy(s,"-");
    }

    strcat(s,m & S_IRUSR ? "r" : "-");
    strcat(s,m & S_IWUSR ? "w" : "-");
    strcat(s,m & S_IXUSR ? "x" : "-");
    strcat(s,m & S_IRGRP ? "r" : "-");
    strcat(s,m & S_IWGRP ? "w" : "-");
    strcat(s,m & S_IXGRP ? "x" : "-");
    strcat(s,m & S_IROTH ? "r" : "-");
    strcat(s,m & S_IWOTH ? "w" : "-");
    strcat(s,m & S_IXOTH ? "x" : "-");
    return s;
}
//转换时间 time_t --->  年月日时分表
char* ttos(time_t t){
    static char time[20];
    struct tm* l = localtime(&t);
    sprintf(time,"%04d-%02d-%02d %02d:%02d:%02d",
                l->tm_year + 1900,l->tm_mon + 1,l->tm_mday,l->tm_hour,l->tm_min,l->tm_sec);
    return time;
}
int main(int argc,char* argv[]){
    //./a.out  hello.txt
    if(argc < 2){
        fprintf(stderr,"用法:%s : <文件名>\n",argv[0]);
        return -1;
    }
    //获取文件的元数据
    // argv[1]  --->  "hello.txt"  
    struct stat s;//用来输出文件的元数据信息
    if(stat(argv[1],&s) == -1){
        perror("stat");
        return -1;
    }
    //输出获取到的元数据信息    
    printf("        设备ID:%lu\n",s.st_dev);
    printf("       i节点号:%ld\n",s.st_ino);
    printf("    类型和权限:%s\n",mtos(s.st_mode));
    printf("      硬连接数:%lu\n",s.st_nlink);
    printf("        用户ID:%u\n",s.st_uid);
    printf("          组ID:%u\n",s.st_gid);
    printf("    特殊设备ID:%lu\n",s.st_rdev);
    printf("      总字节数:%ld\n",s.st_size);
    printf("    IO块字节数:%ld\n",s.st_size);
    printf("      存储块数:%ld\n",s.st_blksize);
    printf("  最后访问时间:%s\n",ttos(s.st_atime));
    printf("  最后修改时间:%s\n",ttos(s.st_mtime));
    printf("  最后改变时间:%s\n",ttos(s.st_ctime));
    return 0;
}

内存映射文件

内存映射文件比read write函数操作文件更快,且可以通过不同进程操作文件来通信
在这里插入图片描述
在这里插入图片描述

//内存映射文件
#include<stdio.h>
#include<string.h>
#include<sys/mman.h>
#include<fcntl.h>
#include<unistd.h>

int main(void){
    //打开文件
    int fd = open("./fmap.txt",O_RDWR | O_CREAT | O_TRUNC,0664);
    if(fd == -1){
        perror("open");
        return -1;
    }
    //修改文件大小
    if(ftruncate(fd,300) == -1){
        perror("ftruncate");
        return -1;
    }
    //建立映射
    char* start = mmap(NULL,4096,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
    if(start == MAP_FAILED){
        perror("mmap");
        return -1;
    }
    //操作文件  char buf[32]
    strcpy(start,"面包牛奶火腿肠");
    printf("%s\n",start);
    //解除映射
    if(munmap(start,4096) == -1){
        perror("munmap");
        return -1;
    }
    //关闭文件
    close(fd);
    return 0;
}

进程

有关task_struct数据结构简要分析
在这里插入图片描述

进程标识PID

在这里插入图片描述

创建子进程

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

父子进程的关系

  • 由fork产生的子进程是其父进程的不完全副本,子进程在内存中的映像
    除了代码区与父进程共享同一块物理内存,其它各区映射到独立的物理
    内存,但其内容从父进程拷贝。
    在这里插入图片描述
//创建子进程
#include<stdio.h>
#include<unistd.h>//fork

int main(void){
    printf("%d进程:我是父进程,我要创建子进程了\n",getpid());
    //子进程一旦创建成功,fork之后的代码,父进程会执行一遍,子进程也会执行一遍
    pid_t a = fork();
    if(a == -1){
        perror("fork");
        return -1;
    }
    //子进程代码
    if(a == 0){
        printf("%d进程:这是子进程执行的代码\n",getpid());
        return 0;
    }
    //父进程代码
    printf("%d进程:这是父进程执行的代码\n",getpid());
    return 0;

    /*if(a == 0){
        printf("%d进程:铁锅炖大鹅\n",getpid());
        return 0;
    }else{
        printf("%d进程:小鸡炖蘑菇\n",getpid());
        return 0;
    }

    printf("%d进程:废债快乐谁\n",getpid());
    return 0;*/
}

进程的终止

正常终止

  • 1.main函数中返回可令进程终止
    在这里插入图片描述
  • 2.调用exit函数令进程终止
    在这里插入图片描述
    在这里插入图片描述
    习惯上,还经常使用EXIT_SUCCESSEXIT_FAILURE两个宏作为调用exit函数的参数,分别表示成功和失败。它们的值在多数系统中被定义成0和-1,但一般建议使用宏,这样做兼容性更好
    代码示例:
//进程的终止
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
//退出处理函数(遗言函数)
void doit(void){
    printf("遗言也没啥可说的...\n");
}
//退出处理函数
void doit2(int status,void* arg){
    //status 进程的退出码
    printf("status = %d\n",status);
    printf(" arg   = %s\n",(char*)arg);
}

int hahaha(void){
    printf("哈哈哈哈\n");
    //exit(0);
    //_exit(10);
    _Exit(0);
    return 10;
}

int main(void){
    //注册退出处理函数
    atexit(doit);//传话的,将函数地址给内核
    on_exit(doit2,"拜拜~~");
    printf("函数返回%d\n",hahaha());

    return 0;
}

  • 3.调用 exit/ Exit函数令进程终止
    在这里插入图片描述
    在这里插入图片描述
退出处理函数
  • 如果在程序中注册的退出处理函数,那么当程序正常执行结束之前,就会自动调用该函数
  • 可以将程序的收尾性工作,放在退出处理函数中
  • 退出处理函数,需要经过注册才能使用
atexit()

在这里插入图片描述

on_exit()

在这里插入图片描述

代码示例
//进程的终止
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
//退出处理函数(遗言函数)
void doit(void){
    printf("遗言也没啥可说的...\n");
}
//退出处理函数
void doit2(int status,void* arg){
    //status 进程的退出码
    printf("status = %d\n",status);
    printf(" arg   = %s\n",(char*)arg);
}

int hahaha(void){
    printf("哈哈哈哈\n");
    //exit(0);
    //_exit(10);
    _Exit(0);
    return 10;
}

int main(void){
    //注册退出处理函数
    atexit(doit);//传话的,将函数地址给内核
    on_exit(doit2,"拜拜~~");
    printf("函数返回%d\n",hahaha());

    return 0;
}

异常终止

1.系统行为
在这里插入图片描述
2.人为触发
在这里插入图片描述
3.、向进程自己发送信号
在这里插入图片描述

回收子进程

为什么要回收子进程

  • 清除僵尸进程,避免消耗系统资源。
  • 父进程需要等待子进程的终止,以继续后续工作。
  • 父进程需要知道子进程终止的原因
    – 如果是正常终止,那么进程的退出码是多少?
    – 如果是异常终止,那么进程是被那个信号所终止的?

wait()

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码示例

//子进程的回收
#include<stdio.h>
#include<stdlib.h>// exit
#include<unistd.h>
#include<sys/wait.h>// wait

int main(void){
    //创建子进程
    pid_t pid = fork();
    if(pid == -1){
        perror("fork");
        return -1;
    }
    //子进程代码
    if(pid == 0){
        printf("%d进程:我是子进程,不想si\n",getpid());
        //sleep(5);
        //return 300;
        //exit(300);
        //_exit(300);
        //abort();
        char* p = NULL;
        *p = 123;//这里属于异常终止
    }
    //父进程代码
    printf("%d进程:等待子进程结束并收尸\n",getpid());
    int s;//用来输出所回收的子进程的终止状态
    pid_t childpid = wait(&s);
    if(childpid == -1){
        perror("wait");
        return -1;
    }
    printf("%d进程:回收了%d进程的僵尸\n",getpid(),childpid);
    
    if(WIFEXITED(s)){
        printf("正常终止:%d\n",WEXITSTATUS(s));
    }else{
        printf("异常终止:%d\n",WTERMSIG(s));
    }
    return 0;
}




回收多个子进程

//回收多个子进程
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<errno.h>

int main(void){
    //创建多个子进程
    for(int i = 0;i < 5;i++){
        pid_t pid = fork();
        if(pid == -1){
            perror("fork");
            return -1;
        }
        if(pid == 0){
            printf("%d进程:我是子进程\n",getpid());
            sleep(i+1);
            return i+1;
        }
    }

    for(;;){
        int s;//用来输出子进程的终止状态
        pid_t pid = wait(&s);
        if(pid == -1){
            if(errno == ECHILD){
                printf("没有子进程了\n");
                break;
            }else{
                perror("wait");
                return -1;
            }
        }
        printf("%d进程:回收了%d进程的僵尸\n",getpid(),pid);
        if(WIFEXITED(s)){
            printf("正常终止:%d\n",WEXITSTATUS(s));
        }else{
            printf("异常终止:%d\n",WTERMSIG(s));
        }
    }


    return 0;
}

waitpid()

在这里插入图片描述
在这里插入图片描述

代码示例

//以非阻塞方式收尸
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>

int main(void){
    //创建子进程,暂时不结束
    pid_t pid = fork();
    if(pid == -1){
        perror("fork");
        return -1;
    }
    //子进程代码
    if(pid == 0){
        printf("%d进程:我是子进程,你收不了我!\n",getpid());
        sleep(10);
        return 0;
    }
    //父进程代码
    printf("%d进程:我要回收那个子进程\n",getpid());
    for(;;){
        pid_t childpid = waitpid(pid,NULL,WNOHANG);
        if(childpid == -1){
            perror("waitpid");
            return -1;
        }else if(childpid == 0){
            printf("%d进程:子进程在运行,收不了,干点别的去\n",getpid());
            sleep(1);
        }else{
            printf("%d进程:回收了%d进程的僵尸\n",getpid(),childpid);
            break;
        }
    }
    return 0;
}

新进程的创建exec()

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

代码示例

//exec家族函数
#include<stdio.h>
#include<unistd.h>

int main(void){
    printf("%d进程:我要变身了,哈哈哈\n",getpid());

    /*if(execl("/bin/ls","ls","-l","-a","-i",NULL) == -1){
        perror("execl");
        return -1;
    }*/
    /*if(execlp("new","new","hello","123",NULL) == -1){
        perror("execlp");
        return -1;
    }*/
    /*if(execle("./new","new","123","hello",NULL,envp) == -1){
        perror("execle");
        return -1;
    }*/
    char* argv[] = {"new","hello","123",NULL};
    char* envp[] = {"NAME=laozhang","AGE=18","FOOG=铁锅炖大鹅",NULL};
    if(execve("./new",argv,envp) == -1){
        perror("execve");
        return -1;
    }
    //变身成功后,后续代码执行不到
    //printf("%d进程:我变身完成了,嘿嘿嘿\n",getpid());
    return 0;
}

system()

在这里插入图片描述
在这里插入图片描述

代码示例

//system函数演示
#include<stdio.h>
#include<stdlib.h> // system

int main(void){
    //成功,返回new执行结束后的终止状态
    int s = system("./new hello 123");
    if(s == -1){
        perror("system");
        return -1;
    }
    if(WIFEXITED(s)){
        printf("正常终止:%d\n",WEXITSTATUS(s));
    }else{
        printf("异常终止:%d\n",WTERMSIG(s));
    }

    s = system("ls -l --color=auto");
    if(s == -1){
        perror("system");
        return -1;
    }
    if(WIFEXITED(s)){
        printf("正常终止:%d\n",WEXITSTATUS(s));
    }else{
        printf("异常终止:%d\n",WTERMSIG(s));
    }

    return 0;   
}

设置用户1D位的运用

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

信号

信号基础

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

信号处理

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

代码示例

//信号处理
#include<stdio.h>
#include<unistd.h>// getpid()
#include<signal.h>// signal()

typedef void (*sighandler_t)(int);

//信号处理函数,内核负责对该函数的调用,
//并传信号的编号,给函数
void sigfun(int signum){
    printf("%d进程:捕获到%d号信号\n",getpid(),signum);
    printf("晚上吃多了,有点撑....\n");
}

int main(void){
    printf("%d进程:我要开始死循环了\n",getpid());
    //对2号信号进行忽略处理
    sighandler_t ret = signal(SIGINT,SIG_IGN);
    if(ret == SIG_ERR){
        perror("signal");
        return -1;
    }
    printf("ret = %p\n",ret);
    //对2号信号进行捕获处理
    ret = signal(SIGINT,sigfun);
    if(ret == SIG_ERR){
        perror("signal");
        return -1;
    }
    printf("ret = %p\n",ret);
    //对2号信号进行默认操作
    ret = signal(SIGINT,SIG_DFL);
    if(ret == SIG_ERR){
        perror("signal");
        return -1;
    }
    printf("ret = %p\n",ret);
    printf("sigfun = %p\n",ret);
    for(;;);
    return 0;
}

太平间信号

在这里插入图片描述
在这里插入图片描述

代码示例

//太平间信号
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<sys/wait.h>
#include<errno.h>
//信号处理函数,负责对子进程收尸
void sigchild(int signum){
    printf("%d进程:捕获到%d号信号\n",getpid(),signum);
    sleep(2);//假装信号处理函数执行很耗时
    for(;;){
        pid_t pid = waitpid(-1,NULL,WNOHANG);
        if(pid == -1){
            if(errno == ECHILD){
                printf("%d进程:没有子进程\n",getpid());
                break;
            }else{
                perror("waitpid");
                return ;
            }
        }else if(pid == 0){
            printf("%d进程:子进程在运行,没法收\n",getpid());
            break;
        }else{
            printf("%d进程:回收了%d进程的僵尸\n",getpid(),pid);
        }
    }
    /*pid_t pid = wait(NULL);
    if(pid == -1){
        perror("wait");
        return ;
    }
    printf("%d进程;回收了%d进程的僵尸\n",getpid(),pid);*/
    
}

int main(void){
    //对17号信号进行捕获处理
    if(signal(SIGCHLD,sigchild) == SIG_ERR){
        perror("signal");
        return -1;
    }
    //创建多个子进程
    for(int i = 0;i < 5;i++){
        pid_t pid = fork();
        if(pid == -1){
            perror("fork");
            return -1;
        }
        if(pid == 0){
            printf("%d进程;我是子进程\n",getpid());
            //sleep(i+1);
            sleep(1);
            return 0;
        }
    }
    //父进程忙自己的事
    for(;;);

    return 0;
}

信号的发送

在这里插入图片描述
在这里插入图片描述

代码示例

//kill 函数
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<errno.h>
#include<sys/wait.h>
//信号处理函数
void sigfun(int signum){
    printf("%d进程:捕获到%d号信号\n",getpid(),signum);
}

int main(void){
    //创建子进程
    pid_t pid = fork();
    if(pid == -1){
        perror("fork");
        return -1;
    }
    //子进程
    if(pid == 0){
        //对2号信号进行捕获处理
        /*if(signal(SIGINT,sigfun) == SIG_ERR){
            perror("signal");
            return -1;
        }*/
        printf("%d进程:我是子进程\n",getpid());
        for(;;);
        return 0;
    }
    //父进程代码,发送2号信号给子进程
    getchar();
    //杀死子进程
    if(kill(pid,SIGINT) == -1){
        perror("kill");
        return -1;
    }

    getchar();
    //判断子进程是否存在
    if(kill(pid,0) == -1){
        if(errno == ESRCH){
            printf("子进程不存在\n");
        }else{
            perror("kill");
            return -1;
        }   
    }else{
        printf("子进程存在\n");
    }
    //收尸
    getchar();
    if(wait(NULL) == -1){
        perror("wait");
        return -1;
    }
    //判断子进程是否存在
    if(kill(pid,0) == -1){
        if(errno == ESRCH){
            printf("子进程不存在\n");
        }else{
            perror("kill");
            return -1;
        }   
    }else{
        printf("子进程存在\n");
    }
        
    return 0;
}

在这里插入图片描述
只有收尸了子进程才不存在
在这里插入图片描述

子进程信号继承

在这里插入图片描述

暂停

在这里插入图片描述

//pause函数演示
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<errno.h>
//信号处理函数
void sigfun(int signum){
    printf("%d号信号处理开始\n",signum);
    sleep(3);
    printf("%d号信号处理结束\n",signum);
}
int main(void){
    //捕获2号信号
    if(signal(SIGINT,sigfun) == SIG_ERR){
        perror("signal");
        return -1;
    }
    printf("%d进程:我要睡觉了\n",getpid());
    int ret = pause();
    printf("%d进程:pause函数返回%d,errno=%d\n",getpid(),ret,errno);
    return 0;
}

在这里插入图片描述
查man手册可知
在这里插入图片描述
errno=4 即为EINTR

信号集sigset

在这里插入图片描述
在这里插入图片描述

sigfillset()

在这里插入图片描述

sigemptyset

在这里插入图片描述

sigaddset

在这里插入图片描述

sigdelset

在这里插入图片描述

sigismember

在这里插入图片描述

代码示例

//信号集操作
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
//实现一个字节的打印
void printb(char byte){
    for(int i = 0;i < 8;i++){
        printf("%d",byte & 1 << 7 - i ? 1 : 0);
    }
    printf(" ");//空格
}
//重复,实现一块存储区所有字节的打印
void printm(void* buf,size_t size){
    for(int i = 0;i < size;i++){
        //倒叙输出
        printb(((char*)buf)[size-1-i]);
        //8个字节换一行
        if((i+1) % 8 == 0){
            printf("\n");
        }
    }
}
int main(void){
    //信号集变量
    sigset_t s;
    printf("填满信号集\n");
    sigfillset(&s);
    printm(&s,sizeof(s));
    printf("清空信号集\n");
    sigemptyset(&s);
    printm(&s,sizeof(s));
    printf("添加2号信号\n");
    sigaddset(&s,SIGINT);
    printm(&s,sizeof(s));
    printf("添加3号信号\n");
    sigaddset(&s,SIGQUIT);
    printm(&s,sizeof(s));
    /*if(sigaddset(&s,32) == -1){
        perror("sigaddset");
        return -1;
    }*/
    printf("删除2号信号\n");
    sigdelset(&s,SIGINT);
    printm(&s,sizeof(s));
    printf("信号集中%s2号信号\n",sigismember(&s,SIGINT) ? "有" : "无");    
    printf("信号集中%s3号信号\n",sigismember(&s,SIGQUIT) ? "有" : "无");    
    return 0;
}

在这里插入图片描述

信号屏蔽

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

sigprocmask

在这里插入图片描述

sigpending

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码示例


//信号屏蔽
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
//假装更新数据库
void updatedb(void){
    for(int i = 0;i < 5;i++){
        printf("正在更新第%d条数据.....\n",i + 1);
        sleep(1);
    }
}
//信号处理函数
void sigfun(int signum){
    printf("捕获到%d号信号\n",signum);
}

int main(void){
    int signum = SIGINT ;
    //父进程对2号信号进行捕获处理
    printf("%d进程:捕获%d号信号\n",getpid(),signum);
    if (signal(signum,sigfun) == SIG_ERR){
        perror("signal");
        return -1;
    }
    //父进程屏蔽2号信号
    printf("%d进程:屏蔽%d号信号\n",getpid(),signum);
    sigset_t sigset;
    sigemptyset(&sigset);//清空
    sigaddset(&sigset,signum);//添加2号信号
    sigset_t oldset;//用来输出以前的信号掩码
    if(sigprocmask(SIG_SETMASK,&sigset,&oldset) == -1){
        perror("sigprocmask");
        return -1;
    }
    //父进程创建子进程
    pid_t pid = fork();
    if(pid == -1){
        perror("fork");
        return -1;
    }
    //子进程负责向父进程多次发送2号信号
    if(pid == 0){
        for(int i = 0;i < 5;i++){
            printf("%d进程:给父进程发送%d号信号\n",getpid(),signum);
            if(kill(getppid(),signum) == -1){
                perror("kill");
                return -1;
            }
        }
        return 0;
    }
    //父进程开始更新数据库
    updatedb();
    //父进程解除对2号信号的屏蔽
    printf("%d进程:解除对%d号信号的屏蔽\n",getpid(),signum);
    if(sigprocmask(SIG_SETMASK,&oldset,NULL) == -1){
        perror("sigprocmask");
        return -1;
    }

    for(;;);
    return 0;
}


















在这里插入图片描述

  • 解释
    由于父进程屏蔽了2号信号,导致父进程在收到第一条2号信号后,会将后续的2号信号屏蔽,待到父进程执行到解除屏蔽2号信号,才能捕获到2号信号。信号屏蔽和忽略不一样,屏蔽只是暂时挡在信号处理前,等解除屏蔽后信号依旧会传递

闹钟与信号

在这里插入图片描述

进程间通信

进程间为什么需要通信

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

进程间通信种类

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

有名管道

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

mkfifo

在这里插入图片描述

代码示例

写有名管道
//写入有名管道
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/stat.h>

int main(void){
    //创建有名管道文件
    printf("创建有名管道文件\n");
    if(mkfifo("./fifo",0664) == -1){
        perror("mkfifo");
        return -1;
    }
    //打开有名管道文件
    printf("打开有名管道文件\n");
    int fd = open("./fifo",O_WRONLY);
    if(fd == -1){
        perror("open");
        return -1;
    }
    //循环多次向文件中写入数据
    printf("发送数据\n");
    for(;;){
        //通过键盘获取数据
        char buf[64] = {};
        fgets(buf,sizeof(buf),stdin);
        //人为指定退出条件,输入!则退出循环
        if(strcmp(buf,"!\n") == 0){
            break;
        }
        //写入到文件中
        if(write(fd,buf,strlen(buf)) == -1){
            perror("write");
            return -1;
        }
    }
    //关闭有名管道文件
    printf("关闭有名管道文件\n");
    close(fd);
    //删除有名管道文件
    printf("删除有名管道文件\n");
    unlink("./fifo");
    printf("大功告成\n");
    return 0;
}






读有名管道
//读取有名管道
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>

int main(void){
    //打开有名管道文件
    printf("打开有名管道文件\n");
    int fd = open("./fifo",O_RDONLY);
    if(fd == -1){
        perror("open");
        return -1;
    }
    //读取有名管道文件
    printf("接受数据\n");
    for(;;){
        char buf[64] = {};
        //从管道中读取数据
        ssize_t size = read(fd,buf,sizeof(buf)-1);
        if(size == -1){
            perror("read");
            return -1;
        }
        if(size == 0){
            printf("对方关闭管道文件\n");
            break;
        }
        //输出
        printf("%s",buf);
    }
    //关闭有名管道文件
    printf("关闭有名管道文件\n");
    close(fd);
    printf("大功告成\n");
    return 0;
}

无名管道

pipe

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

特殊情况

在这里插入图片描述
在这里插入图片描述

代码示例

//无名管道
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>

int main(void){
    //父进程创建无名管道
    printf("%d进程:创建无名管道\n",getpid());
    int pipefd[2];// 用来输出无名官道的读端和写端描述符
    if(pipe(pipefd) == -1){
        perror("pipe");
        return -1;
    }
    printf("pipefd[0] = %d\n",pipefd[0]);
    printf("pipefd[1] = %d\n",pipefd[1]);
    //父进程创建子进程
    pid_t pid = fork();
    if(pid == -1){
        perror("fork");
        return -1;
    }
    //子进程负责读取数据
    if(pid == 0){
        printf("%d进程:关闭写端\n",getpid());
        close(pipefd[1]);
        printf("%d进程:接受数据\n",getpid());
        for(;;){
            char buf[64] = {};
            //通过管道的读端描述符,读取数据
            ssize_t size = read(pipefd[0],buf,sizeof(buf)-1);
            if(size == -1){
                perror("read");
                return -1;
            }
            if(size == 0){
                printf("%d进程:写端被关闭\n",getpid());
                break;
            }
            //显示
            printf("%s",buf);
        }
        printf("%d进程:关不读端\n",getpid());
        close(pipefd[0]);
        printf("%d进程:大功告成\n",getpid());
        return 0;
    }
    //父进程负责写入数据
    printf("%d进程:关闭读端\n",getpid());
    close(pipefd[0]);
    printf("%d进程:发送数据\n",getpid());
    for(;;){
        //从键盘获取数据
        char buf[64] = {};
        fgets(buf,sizeof(buf),stdin);
        
        if(strcmp(buf,"!\n") == 0){
            break;
        }

        //通过写端描述符,向无名管道写入数据
        if(write(pipefd[1],buf,strlen(buf)) == -1){
            perror("write");
            return -1;
        }
    }
    printf("%d进程:关闭写端\n",getpid());
    close(pipefd[1]);
    //父进程收尸
    if(wait(NULL) == -1){
        perror("wait");
        return -1;
    }
    printf("%d进程:已收尸\n",getpid());
    printf("%d进程:大功告成\n",getpid());
    return 0;
}

管道符号 |

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

ipc对象

在这里插入图片描述
在这里插入图片描述

ipc命令

在这里插入图片描述

ftok

在这里插入图片描述
在这里插入图片描述

共享内存

在这里插入图片描述
在这里插入图片描述

创建共享内存shmget

在这里插入图片描述

加载共享内存shmat

在这里插入图片描述
在这里插入图片描述

卸载共享内存shmdt

在这里插入图片描述

销毁共享内存shmctl

在这里插入图片描述

共享内存编程模型

在这里插入图片描述

参考代码

向共享内存中写入数据
//向共享内存中写入数据
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/shm.h>

int main(void){
    //合成键
    printf("%d进程:合成键\n",getpid());
    key_t key = ftok(".",123);
    if(key == -1){
        perror("ftok");
        return -1;
    }
    //创建共享内存
    printf("%d进程:创建共享内存\n",getpid());
    int shmid = shmget(key,4096,IPC_CREAT | IPC_EXCL | 0664);
    if(shmid == -1){
        perror("shmget");
        return -1;
    }
    //加载共享内存
    printf("%d进程:加载共享内存\n",getpid());
    char* start = shmat(shmid,NULL,0);
    if(start == (void*)-1){
        perror("shmat");
        return -1;
    }
    //写入数据
    sprintf(start,"shmid=%d,key=0x%x,pid=%d\n",shmid,key,getpid());
    //卸载共享内存
    printf("%d进程:卸载共享内存\n",getpid());
    getchar();
    if(shmdt(start) == -1){
        perror("shmdt");
        return -1;
    }
    //销毁共享内存
    printf("%d进程:销毁共享内存\n",getpid());
    getchar();
    if(shmctl(shmid,IPC_RMID,NULL) == -1){
        perror("shmctl");
        return -1;
    }
    printf("%d进程:大功告成\n",getpid());
    return 0;
}

读取共享内存
//读取共享内存
#include<stdio.h>
#include<unistd.h>
#include<sys/shm.h>

int main(void){
    //合成键
    printf("%d进程:合成键\n",getpid());
    key_t key = ftok(".",123);
    if(key == -1){
        perror("ftok");
        return -1;
    }
    //获取共享内存
    printf("%d进程:获取共享内存\n",getpid());
    int shmid = shmget(key,0,0);
    if(shmid == -1){
        perror("shmget");
        return -1;
    }
    //加载共享内存
    printf("%d进程:加载共享内存\n",getpid());
    char* start = shmat(shmid,NULL,0);
    if(start == (void*)-1){
        perror("start");
        return -1;
    }
    //读取数据
    getchar();
    printf("%s\n",start);
    //卸载共享内存
    printf("%d进程:卸载共享内存\n",getpid());
    getchar();
    if(shmdt(start) == -1){
        perror("shmdt");
        return -1;
    }
    printf("%d进程:大功告成\n",getpid());
    return 0;
}


消息队列

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

创建或获取消息队列msgget

在这里插入图片描述

发送消息msgsnd

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

接收消息 msgrcv

在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

销毁消息队列msgctl

在这里插入图片描述

消息队列编程模型

在这里插入图片描述

代码示例

向消息队列写入数据
//向消息队列写入数据
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/msg.h>

int main(void){
    //合成键
    printf("%d进程:合成键\n",getpid());
    key_t key = ftok(".",123);
    if(key == -1){
        perror("ftok");
        return -1;
    }
    //创建消息队列
    printf("%d进程:创建消息队列\n",getpid());
    int msgid = msgget(key,IPC_CREAT | IPC_EXCL | 0664);
    if(msgid == -1){
        perror("msgget");
        return -1;
    }
    //发送数据
    printf("%d进程:发送消息\n",getpid());
    for(;;){
        struct {
            long type;//消息类型
            char data[64];//数据内容
        } buf = {1234,""};//"" 空串
        //通过键盘获取数据
        fgets(buf.data,sizeof(buf.data),stdin);

        if(strcmp(buf.data,"!\n") == 0){
            break;
        }

        //发送消息
        if(msgsnd(msgid,&buf,strlen(buf.data),0) == -1){
            perror("msgsnd");
            return -1;
        }
    }
    //销毁消息队列
    printf("%d进程:销毁消息队列\n",getpid());
    if(msgctl(msgid,IPC_RMID,NULL) == -1){
        perror("msgctl");
        return -1;
    }
    printf("%d进程:大功告成\n",getpid());
    return 0;
}

向消息队列读取数据
//读取数据
#include<stdio.h>
#include<unistd.h>
#include<sys/msg.h>
#include<errno.h>
int main(void){
    //合成键
    printf("%d进程:合成键\n",getpid());
    key_t key = ftok(".",123);
    if(key == -1){
        perror("ftok");
        return -1;
    }
    //获取消息队列
    printf("%d进程:获取消息队列\n",getpid());
    int msgid = msgget(key,0);
    if(msgid == -1){
        perror("msgget");
        return -1;
    }
    //接受消息
    printf("%d进程:接受数据\n",getpid());
    for(;;){
        struct {
            long type;//消息类型
            char data[64];//消息内容
        } buf = {};
        ssize_t size =  msgrcv(msgid,&buf,sizeof(buf.data)-1,1234,0);
        if(size == -1){
            if(errno == EIDRM){
                printf("%d进程:消息队列被销毁\n",getpid());
                break;
            }else{
                perror("msgrcv");
                return -1;
            }
        }
        printf("%ld>>%s",buf.type,buf.data);
    }
    printf("%d进程:大功告成\n",getpid());
    return 0;
}

网络

在这里插入图片描述

ip地址

在这里插入图片描述
在这里插入图片描述

套接字

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

创建套接字socket

在这里插入图片描述
在这里插入图片描述

套接字相关地址结构体参数

sockaddr

在这里插入图片描述

sockaddr_un 本地地址结构&& sockaddr_in 网络地址结构(重点)

在这里插入图片描述
在这里插入图片描述

将套接字与本机地址绑定bind

在这里插入图片描述

将套接字与对方地址绑定connect

在这里插入图片描述

字节序转换

在这里插入图片描述

转换函数

在这里插入图片描述在这里插入图片描述

TCP函数

启动侦听 listen

在这里插入图片描述

等待连接请求accept

在这里插入图片描述

接收数据recv

在这里插入图片描述

发送数据send

在这里插入图片描述

TCP编程模型

在这里插入图片描述

通信中止

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码示例

基于TCP的客户端
//基于TCP的客户端
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>

int main(void){
    //创建套接字
    printf("客户端:创建套接字\n");
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd == -1){
        perror("socket");
        return -1;
    }
    //组织地址服务器的地址结构
    printf("客户端:组织地址服务器的地址结构\n");
    struct sockaddr_in ser;
    ser.sin_family = AF_INET;
    ser.sin_port = htons(8888);
    ser.sin_addr.s_addr = inet_addr("192.168.222.128");
    //发起连接请求
    printf("客户端:发起连接请求\n");
    if(connect(sockfd,(struct sockaddr*)&ser,sizeof(ser)) == -1){
        perror("connect");
        return -1;
    }
    //业务处理
    printf("客户端:业务处理\n");
    for(;;){
        //获取小写的串
        char buf[128] = {};
        fgets(buf,sizeof(buf),stdin);

        if(strcmp(buf,"!\n") == 0){
            break;
        }

        //发送给服务器
        if(send(sockfd,buf,strlen(buf),0) == -1){
            perror("send");
            return -1;
        }
        //接受大写的串
        if(recv(sockfd,buf,sizeof(buf)-1,0) == -1){
            perror("recv");
            return -1;
        }
        //显示输出
        printf(">>> %s",buf);
    }
    printf("客户端:关闭套接字\n");
    close(sockfd);
    return 0;
}








基于TCP协议的客户端和服务器
//基于TCP协议的客户端和服务器
#include<stdio.h>
#include<string.h>
#include<ctype.h> // toupper
#include<unistd.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<signal.h>
#include<sys/wait.h>
#include<errno.h>
//信号处理函数
void sigchild(int signum){
    for(;;){
        pid_t pid = waitpid(-1,NULL,WNOHANG);
        if(pid == -1){
            if(errno == ECHILD){
                printf("服务器:没有子进程了\n");
                break;
            }else{
                perror("waitpid");
                return ;
            }
        }else if(pid == 0){
            printf("服务器:子进程在运行,收不了\n");
            break;
        }else{
            printf("服务器:回收了%d的僵尸\n",pid);
        }
    }
}

int main(void){
    //对17号信号进行捕获处理
    if(signal(SIGCHLD,sigchild) == SIG_ERR){
        perror("signal");
        return -1;
    }
    //创建套接字,买了个手机
    printf("服务器:创建套接字\n");
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd == -1){
        perror("socket");
        return -1;
    }
    //组织地址结构 办张电话卡
    printf("服务器:组织地址结构\n");
    struct sockaddr_in ser ;
    ser.sin_family = AF_INET;
    ser.sin_port = htons(8888);//字节序转换
    //ser.sin_addr.s_addr = inet_addr("192.168.222.128");//串-->整数
    ser.sin_addr.s_addr = INADDR_ANY;//接受任意IP地址到来的数据
    //绑定套接字和地址结构,手机卡插入手机
    printf("服务器:绑定套接字和地址结构\n");
    if(bind(sockfd,(struct sockaddr*)&ser,sizeof(ser)) == -1){
        perror("bind");
        return -1;
    }
    //启动侦听,从主动变被动,可接受连接请求
    printf("服务器:启动侦听\n");
    if(listen(sockfd,1024) == -1){
        perror("listen");
        return -1;
    }
    //等待连接,等待客户端的连接请求到来,接受连接请求,完成三次握手
    for(;;){
        printf("服务器:等待客户端的连接请求到来\n");
        struct sockaddr_in cli;//用来输出客户端的地址结构
        socklen_t len = sizeof(cli);//输出地址结构的大小
        int conn = accept(sockfd,(struct sockaddr*)&cli,&len);//等待连接请求
        if(conn == -1){
            perror("accept");
            return -1;
        }
        //创建子进程,负责和客户端通信
        pid_t pid = fork();
        if(pid == -1){
            perror("fork");
            return -1;
        }
        if(pid == 0){
            //关闭侦听套接字
            close(sockfd);
            //业务处理
            printf("服务器:业务处理\n");
            for(;;){
                //接客户端发来的小写的串
                char buf[128] = {};
                ssize_t size = read(conn,buf,sizeof(buf)-1);
                if(size == -1){
                    perror("read");
                    return -1;
                }
                if(size == 0){
                    //客户端关闭套接字
                    break;
                }
                //转成大写
                for(int i = 0;i < strlen(buf);i++){
                    buf[i] = toupper(buf[i]);
                }
                //将大写的串发会给客户端    
                if(write(conn,buf,strlen(buf)) == -1){
                    perror("write");
                    return -1;
                }
            }
            //关闭套接字
            printf("服务器:关闭套接字\n");
            close(conn);
            return 0;
        }
        //父进程代码
        //关闭通信他戒子
        close(conn);
    }
    close(sockfd);
    return 0;
}


UDP

recvfrom

在这里插入图片描述

sendto

在这里插入图片描述

编程模型

在这里插入图片描述

代码实现

客户端
//基于udp的客户端
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>

int main(void){
    //创建套接字
    printf("客户端:创建套接字\n");
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    if(sockfd == -1){
        perror("socket");
        return -1;
    }
    //组织服务器地址结构
    printf("客户端:组织服务器地址结构\n");
    struct sockaddr_in ser;
    ser.sin_family = AF_INET;
    ser.sin_port = htons(9999);
    ser.sin_addr.s_addr = inet_addr("192.168.222.128");
    //业务处理
    printf("客户端:业务处理\n");
    for(;;){
        //通过键盘获取小写的串
        char buf[128] = {};
        fgets(buf,sizeof(buf),stdin);

        if(strcmp(buf,"!\n") == 0){
            break;
        }

        //发送给服务器
        if(sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&ser,sizeof(ser)) == -1){
            perror("sendto");
            return -1;
        }
        //接受回传的数据
        if(recv(sockfd,buf,sizeof(buf)-1,0) == -1){
            perror("recv");
            return -1;
        }
        //显示
        printf("%s",buf);
    }
    //关闭套接字
    printf("客户端:关闭套接字\n");
    close(sockfd);
    return 0;
}





服务端
//基于udp的客户端
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>

int main(void){
    //创建套接字
    printf("客户端:创建套接字\n");
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    if(sockfd == -1){
        perror("socket");
        return -1;
    }
    //组织服务器地址结构
    printf("客户端:组织服务器地址结构\n");
    struct sockaddr_in ser;
    ser.sin_family = AF_INET;
    ser.sin_port = htons(9999);
    ser.sin_addr.s_addr = inet_addr("192.168.222.128");
    //业务处理
    printf("客户端:业务处理\n");
    for(;;){
        //通过键盘获取小写的串
        char buf[128] = {};
        fgets(buf,sizeof(buf),stdin);

        if(strcmp(buf,"!\n") == 0){
            break;
        }

        //发送给服务器
        if(sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&ser,sizeof(ser)) == -1){
            perror("sendto");
            return -1;
        }
        //接受回传的数据
        if(recv(sockfd,buf,sizeof(buf)-1,0) == -1){
            perror("recv");
            return -1;
        }
        //显示
        printf("%s",buf);
    }
    //关闭套接字
    printf("客户端:关闭套接字\n");
    close(sockfd);
    return 0;
}





HTTP协议

在这里插入图片描述

HTTP的请求和响应

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码示例

//客户端,向百度服务器发http请求
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>

int main(int argc,char* argv[]){
    // ./a.out <IP地址> <域名> [<资源路径>] 
    if(argc < 3){
        fprintf(stderr,"用法:%s <IP地址> <域名> [<资源路径>]\n",argv[0]);
        return -1;
    }
    char* ip = argv[1];
    char* name = argv[2];
    char* path = argc < 4 ? "" : argv[3];
    //创建套接字
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd == -1){
        perror("socket");
        return -1;
    }
    //组织服务器地址结构
    struct sockaddr_in ser;
    ser.sin_family = AF_INET;
    ser.sin_port = htons(80);
    ser.sin_addr.s_addr = inet_addr(ip);
    //发起连接
    if(connect(sockfd,(struct sockaddr*)&ser,sizeof(ser)) == -1){
        perror("connect");
        return -1;
    }
    //组织请求
    char request[1024] = {};
    sprintf(request,"GET /%s HTTP/1.1\r\n"
                    "Host: %s\r\n"
                    "Accept: */*\r\n"
                    "Connection: close\r\n"
                    "User-Agent: Mozilla/5.0\r\n\r\n",path,name);
    //发送请求
    if(send(sockfd,request,strlen(request),0) == -1){
        perror("send");
        return -1;
    }
    //接受响应
    for(;;){
        char respond[1024] = {};
        ssize_t size = recv(sockfd,respond,sizeof(respond)-1,0);
        if(size == -1){
            perror("recv");
            return -1;
        }
        if(size == 0){
            break;
        }
        printf("%s",respond);
    }
    printf("\n");
    //关闭套接字
    close(sockfd);
    return 0;
}

线程

线程概念

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

POSIX线程

注意!!!

  1. g++/gcc编译的时候需要加上参数-lpthread,编译pthread动态库。
  2. 不应使用全局变量 pthread_t tid,在子线程中通过pthread_create传出参数来获取线程ID,而应使用pthread_self。
  3. 线程ID:pthread_t类型,可理解为:typedef unsigned long int pthread_t;本质:在Linux下为无符号整数(%lu),其他系统中可能是结构体实现
  4. 线程ID是进程内部,识别标志。(两个进程间,线程ID允许相同)

获取线程ID函数pthread_self

  • 头文件:
      #include <pthread.h>
  • 函数原型:
      pthread_t pthread_self(void);
  • 函数参数:
      无
  • 返回值:
      成功:返回线程的ID号;

创建线程pthread_create

在这里插入图片描述

第二个参数属性

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

代码示例
//线程的创建
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
//线程过程函数
void* pthread_fun(void* arg){
    printf("%lu线程:我是子线程 --> %s\n",pthread_self(),(char*)arg);
    return NULL;
}

int main(void){
    printf("%lu线程:我是主线程,我要创建子线程\n",pthread_self());
    pthread_t tid;//用来输出线程的ID
    int error = pthread_create(&tid,NULL,pthread_fun,"老张最帅!");
    if(error){
        fprintf(stderr,"pthread_create:%s\n",strerror(error));
        return -1;
    }
    sleep(1);
    printf("%lu线程:我是主线程,我创建了%lu线程\n",pthread_self(),tid);
    return 0;
}

线程过程函数thread_ proc

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

汇合线程(可以接线程返回值)

在这里插入图片描述
在这里插入图片描述

代码示例

//线程的汇入
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#define PI 3.14
//线程过程函数,计算圆的面积
void* area(void* arg){
    double r = *(double*)arg;
    static double s ;
    s = PI * r * r;
    sleep(5);
    return &s;
}

int main(void){
    double r = 10;
    pthread_t tid;
    pthread_create(&tid,NULL,area,&r);
    
    double* area;//接线程的返回值
    pthread_join(tid,(void**)&area);
    printf("圆的面积是%lg\n",*area);

    return 0;
}

分离线程

在这里插入图片描述
在这里插入图片描述

//分离线程
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
//线程过程函数
void* pthread_fun(void* arg){
    for(int i = 0;i < 100;i++){
        putchar('-');//getchar()
        usleep(50000);
    }
    return NULL;
}

int main(void){
    setbuf(stdout,NULL);//相当于关闭输出缓冲区
    pthread_t tid;
    pthread_create(&tid,NULL,pthread_fun,NULL);
    //设置分离线程
    pthread_detach(tid);
    /*int error = pthread_join(tid,NULL);//NULL 无返回值
    if(error){
        fprintf(stderr,"pthread_join:%s\n",strerror(error));
        return -1;
    }*/
    for(int i = 0;i < 100;i++){
        putchar('+');
        usleep(100000);
    }
    return 0;
}


并发冲突

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

线程同步

在这里插入图片描述

互斥锁

在这里插入图片描述

初始化互斥锁

在这里插入图片描述

销毁互斥锁

在这里插入图片描述

锁定互斥体

在这里插入图片描述

解锁互斥锁

在这里插入图片描述

代码示例

//并发冲突
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
int g_cn = 0;//全局变量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//互斥锁
//线程过程函数
void* pthread_add(void* arg){
    //加锁
    //pthread_mutex_lock(&mutex);
    for(int i = 0;i < 1000000;i++){
        //加锁
        pthread_mutex_lock(&mutex);
        g_cn++;
        //解锁
        pthread_mutex_unlock(&mutex);
    }
    //解锁
    //pthread_mutex_unlock(&mutex);
    return NULL;
}

int main(void){
    pthread_t tid1,tid2;
    pthread_create(&tid1,NULL,pthread_add,NULL);
    pthread_create(&tid2,NULL,pthread_add,NULL);
    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);
    printf("g_cn = %d\n",g_cn);
    return 0;
}

条件变量

在这里插入图片描述

初始化条件变量

在这里插入图片描述

销毁条件变量

在这里插入图片描述

睡入条件变量

在这里插入图片描述
在这里插入图片描述

唤醒条件变量

在这里插入图片描述
在这里插入图片描述

生产者消费者问题

在这里插入图片描述
在这里插入图片描述

代码示例

//条件变量解决生产者和消费者问题
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
char g_storage[10];//仓库
int g_stock = 0;//当前库存量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//互斥锁
pthread_cond_t pcond = PTHREAD_COND_INITIALIZER;//生产者卧室
pthread_cond_t ccond = PTHREAD_COND_INITIALIZER;//消费者卧室
//显示库存情况  生产者:ABC<--D   消费者:ABC-->D
void show(char* who,char* op,char prod){
    printf("%s:",who);
    for(int i = 0;i < g_stock;i++){
        printf("%c",g_storage[i]);
    }
    printf("%s%c\n",op,prod);
}
//生产者线程
void* producer(void* arg){
    char* who = (char*)arg;
    for(;;){
        //加锁
        pthread_mutex_lock(&mutex);
        //判断
        if(g_stock == 10){//满
            printf("%s:满仓\n",who);
            pthread_cond_wait(&pcond,&mutex);
        }
        //生产
        char prod = 'A' + rand() % 26;
        show(who,"<--",prod);
        g_storage[g_stock] = prod;
        g_stock++;
        //唤醒消费者
        pthread_cond_signal(&ccond);
        //解锁
        pthread_mutex_unlock(&mutex);
        usleep((rand() % 100) * 1000);
    }
    return NULL;
}
//消费者线程
void* customer(void* arg){
    char* who = (char*)arg;
    for(;;){
        //加锁
        pthread_mutex_lock(&mutex);
        //判断
        if(g_stock == 0){//空
            printf("%s:空仓\n",who);
            pthread_cond_wait(&ccond,&mutex);
        }
        //消费
        char prod = g_storage[--g_stock];
        show(who,"-->",prod);
        //唤醒生产者
        pthread_cond_signal(&pcond);
        //解锁
        pthread_mutex_unlock(&mutex);
        usleep((rand() % 100) * 1000);
    }
    return NULL;
}

int main(void){
    pthread_t t1,t2;
    pthread_create(&t1,NULL,producer,"生产者");
    pthread_create(&t2,NULL,customer,"消费者");

    getchar();
    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值