webserver笔记1-3

webserver笔记

第1章 Linux系统编程入门

g++执行的四个过程

一、预处理:条件编译,头文件包含,宏替换的处理,生成.i文件。

二、编译:将预处理后的文件转换成汇编语言,生成.s文件

三、汇编:汇编变为目标代码(机器代码)生成.o的文件

四、连接:链接目标代码,生成可执行程序

常见文件后缀c++

​ .c为后缀的文件:c语言源代码文件小程序

​ .a为后缀的文件:是由目标文件构成的库文件vim

​ .cpp为后缀的文件:是c++源代码文件编辑器

​ .h为后缀的文件:头文件函数

​ .o为后缀的文件:是编译后的目标文件工具

​ .s为后缀的文件:是汇编语言源代码文件学习

​ .m为后缀的文件:Objective-C原始程序spa

​ .so为后缀的文件:编译后的动态库文件

1.1 gcc命令

# -o 表示要生成的文件名
gcc test.c					#直接生成test.out
gcc test.c -o app			#生成可执行文件app
gcc test.c -E -o test.i 	#编译预处理
gcc test.i -S -o test.s		#编译成汇编语言
gcc test.s -c -o test.o		#汇编
gcc test.o -o test.out		#链接成可执行程序

webserver笔记1-3_第1张图片

webserver笔记1-3_第2张图片

1.2 gcc和g++

webserver笔记1-3_第3张图片

webserver笔记1-3_第4张图片

1.3 静态库制作

webserver笔记1-3_第5张图片

webserver笔记1-3_第6张图片webserver笔记1-3_第7张图片

webserver笔记1-3_第8张图片

gcc -c add.c div.c mult.c sub.c				 #将c编译汇编为 xxx.o
ar rcs libcalc.a add.o div.c mult.c sub.c 	 #将xxx.o打包成 libxxx.a的静态库

1.4 静态库使用

分发静态库时需要把头文件一起分发,导入时需要导入头文件使用静态库

gcc main.c -o app -I ./include/ -l calc -L ./lib/	
#-I 搜索头文件位置;-l指定库名(b);-L 指定库位置

1.5 动态库制作

webserver笔记1-3_第9张图片
webserver笔记1-3_第10张图片

gcc -c -fpic add.c div.c mult.c sub.c	#将c编译汇编为与地址无关的 xxx.o
gcc -shared *.o -o libcalc.so		

1.6 动态库使用

分发动态库时需要把头文件一起分发,导入时需要导入头文件使用动态库

错误❌
gcc main.c -o main -I include/ -L lib/ -l calc
---error while loading shared libraries: libcalc.so: cannot open shared object file: No such file or directory

1.7 动态库加载失败

webserver笔记1-3_第11张图片

>ldd main		#查看动态库依赖关系
linux-vdso.so.1 (0x00007ffea21b7000)
        libcalc.so => not found
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f6ee9bfc000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f6eea1ef000)

修改环境变量

解决方案1:临时

>export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/liukunpeng/linux/lesson02/library/lib
#命令行修改环境变量LD_LIBRARY_PATH 但是关闭终端即失效
>echo $LD_LIBRARY_PATH 		#查询环境变量值
--:/home/liukunpeng/linux/lesson02/library/lib

解决方案2:用户级

cd ~		#进入目录找到bashrc文件
vim bashrc		#编辑bashrc文件
#在bashrc文件末尾添加并保存
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/liukunpeng/linux/lesson02/library/lib
. .bashrc 或 source .bashrc	#刷新bashrc

解决方案2:系统级

sudo vim /etc/profile	#文件末尾添加并保存
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/liukunpeng/linux/lesson02/library/lib
source /etc/profile		#刷新

修改/etc/ld.so.cache文件列表

cd /etc/
sudo vim ld.so.conf		#修改文件添加
/home/liukunpeng/linux/lesson02/library/lib		#保存并退出
sudo ldconfig  #刷新

将动态库文件放到 /lib 或 /usr/lib 里(不推荐)

1.8 动态库和静态库对比

webserver笔记1-3_第12张图片

静态库在链接时就将代码复制到可执行文件中。当库比较小时推荐使用静态库。

动态库链接时不会将代码复制到可执行文件中,当api被调用时, 动态库被加载到内存。当库文件比较大时推荐使用动态库。

静态库优缺点:

优点:

  • 静态库被打包到应用程序中加载速度快
  • 发布程序无需提供静态库。移植方便

缺点

  • 消耗系统资源,浪费内存
  • 更新、部署、发布麻烦

动态库优缺点:

优点

  • 可以实现进程间资源共享(共享库)
  • 更新、部署、发布简单
  • 可以控制何时加载动态库

缺点

  • 加载速度比静态库慢
  • 发布程序时需要提供依赖的动态库文件

1.9 Makefile

webserver笔记1-3_第13张图片

webserver笔记1-3_第14张图片

vim Makefile
app: ./calc/sub.c ./calc/add.c ./calc/div.c ./calc/mult.c ./calc/main.c
	gcc ./calc/sub.c ./calc/add.c ./calc/div.c ./calc/mult.c ./calc/main.c -o app
#保存退出
make	#运行makefile
vim Makefile
app: sub.o add.o div.o mult.o main.o		#找不到 xxx.o 读取下面的规则
	gcc sub.o add.o div.o mult.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
div.o: div.c
	gcc -c div.c -o div.o
mult.o: mult.c
	gcc -c mult.c -o mult.o
main.o: main.c					#下面的规则只为第一个规则服务,与第一个无关的不执行
	gcc -c main.c -o main.o

如果只改了其中部分依赖文件,使用第二种方法效率会更高。

如果使用第一种会重新编译其他文件造成浪费。第二种方法只会重新链接就可以。

原理:

变量:

webserver笔记1-3_第15张图片

webserver笔记1-3_第16张图片

vim Makefile
#定义变量
src= sub.o add.o div.o mult.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
div.o: div.c
	gcc -c div.c -o div.o
mult.o: mult.c
	gcc -c mult.c -o mult.o
main.o: main.c					#下面的规则只为第一个规则服务,与第一个无关的不执行
	gcc -c main.c -o main.o

模式匹配

webserver笔记1-3_第17张图片

#定义变量
src= sub.o add.o div.o mult.o main.o
target= app
$(target): $(src)
	$(CC)  $(src) -o $(target)
%.o: %.c
	$(CC) -c $< -o $@
函数:

webserver笔记1-3_第18张图片

src= $(wildcard ./*.c)
#sub.c add.c div.c mult.c main.c

webserver笔记1-3_第19张图片

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

clean: 
	rm $(objs) -f		#删除xxx.o文件,同时生成一个clean文件
make clean		#标注执行clean规则
src=$(wildcard ./*.c)
objs = $(patsubst %.c %.o $(src))
target = app
$(target): $(objs)
	$(CC) $(objs) -o $(target)
%.o: %.c
	$(CC) -c $< -o $@
.PHONY:clean			#表示clean是伪目标,不会生成clean文件
clean: 
	rm $(objs) -f		#删除xxx.o文件

1.10 GDB调试

webserver笔记1-3_第20张图片
webserver笔记1-3_第21张图片

webserver笔记1-3_第22张图片
webserver笔记1-3_第23张图片

gcc test.c -o test -g			
gdb test						#gdb调试test文件,进入gdb
set args 10 20					#设置传参
show args						#显示传入的参数
help							#帮助
quit(或 q)						#退出
list(或 l)						#从默认位置显示代码
l 20							#查看第20行
l main							#查看main函数
l bubble.cpp:10					#查看其他文件指定行号代码
l bubble.cpp:bubbleSort			#查看其他文件中指定函数代码
show list(或 listsize)			#显示行数
set list(或 listsize)			#修改显示行数
break 9							#第9行打断点
info break(或 i b)				#断点信息
break bubble.cpp: bubbleSort	#其他文件指定函数断点
break bubble.cpp:8				#其他文件指定行数断点
del 1							#删除第 i 个断点
dis 1							#设置第 i 个断点不可用
ena 1							#设置第 i 个断点可用
b 16 if i ==3					#16行设置条件断点

webserver笔记1-3_第24张图片

1.11 Linux文件IO

标准C库IO函数

webserver笔记1-3_第25张图片

缓冲区默认大小8K,8192byte

在网络通信时使用Linux自带的IO,对磁盘文件读写时用C库IO

1.12 虚拟地址空间

用来解释内存的模型

webserver笔记1-3_第26张图片

1.13 文件描述符

文件描述符在内核区

文件描述符表默认1024,最大打开1024个文件,前三个默认打开

webserver笔记1-3_第27张图片

1.14 打开文件

/*
bash> man 2 open
    #include 
    #include 
    #include 
    打开一个已经存在的文件
   int open(const char *pathname, int flags);
        pathname: 路径
        flags:操作权限设置 O_RDONLY, O_WRONLY, or O_RDWR
    返回值: 返回文件描述符或者-1
   创建一个新的文件
   int open(const char *pathname, int flags, mode_t mode);
    errno:属于Linux系统数据库,库里的一个全局变量,记录最近的错误号
bash> man 3 perror
    #include 
    打印errno对应的错误描述
    void perror(const char *s);
bash> man 2 close
    #include 
    int close(int fd);
*/
#include 
#include 
#include 
#include 
#include 
int main(){
    int fd = open("./a.txt", O_RDONLY);
    if(fd == -1){
        perror("open");
    }
    close(fd);
}

1.15 创建文件

/*
#include 
#include 
#include 
    //创建一个新的文件
   int open(const char *pathname, int flags, mode_t mode);
    -flags: 必选项   O_RDONLY, O_WRONLY, or O_RDWR  三选一
            可选项  O_APPEND, O_ASYNC, O_CREAT(文件不存在则创建)
    -mode: 八进制的数,表示创建出的新文件的操作权限 如:0775
    最终权限: mode & ~umask
    权限:         -rwxrwxrwxrwx
    mode:  0777 --- 000111111111
   ~umask:~0002 --- 111111111101
    最终:  0775 --- 000111111101
*/
#include 
#include 
#include 
#include 
#include 
int main(){
    int fd = open("./test.txt", O_RDONLY | O_CREAT, 0777);
    if(fd == -1){
        perror("create");
    }
    close(fd);
}

1.16 read、write

/*
#include      #unix std
    ssize_t read(int fd, void *buf, size_t count);
    -fd: open得到的文件描述符
    -buf: 读取数据存放的地方,数组或者数组地址
    -count: 指定的数组大小  
    返回值: 成功: >0返回实际的字节数  =0文件读取完了  <0 读取失败且设置errno
    ssize_t write(int fd, const void *buf, size_t count);
    如果第三个参数大于缓冲区实际的大小,那么会把缓冲区后面的内存中的数据写进去,
    只不过这些数据我们是不确定的,是野内存,操作野内存有可能会产生问题,
    所以一般不会这么去做。缓冲区中有多少数据,我们就写多少数据即可。
*/
#include 
#include 
#include 
#include 
#include 
int main(){
    int fd = open("./english.txt", O_RDONLY);
    if(fd == -1){
        perror("read");
        return -1;
    }
    int dstfd = open("./cpy.txt", O_WRONLY | O_CREAT, 0664);
    if(dstfd == -1){
        perror("write");
        return -1;
    }
    char buf[1024] = {0};
    int len = 0;
    while((len = read(fd, buf, sizeof(buf))) > 0){
        write(dstfd, buf, len);
    }
    close(fd);
    close(dstfd);
    return 0;
}

1.17 lseek

/*
#include 
#include 
    off_t lseek(int fd, off_t offset, int whence);
    -offset: 偏移量
    -whence:SEEK_SET:设置文件指针的偏移量
            SEEK_CUR:从当前位置加上偏移量
            SEEK_END:文件的大小加上偏移量
    作用:
    1.移动文件指针到文件开头    lseek(fd, 0, SEEK_SET)
    2.获取当前文件指针的位置    lseek(fd, 0, SEEK_CUR)
    3.文件长度                 lseek(fd, 0, SEEK_END)
    4.拓展文件的长度           lseek(fd, 100, SEEK_END) 下载文件时占用空间
#include 
    int fseek(FILE *stream, long offset, int whence);
*/
#include 
#include 
#include 
#include 
#include 
int main(){
    int fd = open("./hello.txt", O_RDWR);
    if(fd == -1){
        perror("rw");
        return -1;
    }
    int ret = lseek(fd, 100, SEEK_END);
    if(ret == -1) {
        perror("lseek");
        return -1;
    }
    write(fd, " ", 1);	#写个空数据才管用
    close(fd);
    return 0;
}

1.18 stat

webserver笔记1-3_第28张图片

webserver笔记1-3_第29张图片
webserver笔记1-3_第30张图片

/*
#include 
#include 
#include 
    #获取文件相关信息
    int stat(const char *pathname, struct stat *statbuf);
    -pathname: 文件路径
    -statbuf: 结构体变量,传出参数用于保存获取到的文件信息
    返回值:成功返回0,失败返回-1,设置errno
    int lstat(const char *pathname, struct stat *statbuf);
    #获取软链接的信息而不是软链接链接到的文件
*/
#include 
#include 
#include 
#include 
int main(){
    struct stat statbuf;
    int ret = stat("a.txt", &statbuf);
    if(ret == -1){
        perror("stat");
        return -1;
    }
    printf("size: %ld\n", statbuf.st_size);
    return 0;
}

模拟实现ls -l命令

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
//模拟 ls-l 指令
//-rw-rw-r--  1 liukunpeng liukunpeng   17 3月   9 21:11 a.txt

int main(int argc, char *argv[]){
    if(argc < 2){
        printf("%s filename\n", argv[0]);
        return -1;
    }
    struct stat st;
    int ret = stat(argv[1], &st);
    if(ret < 0){
        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 * fileGroup = 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, fileGroup, fileSize, time, argv[1]);
    printf("%s\n", buf);
    return 0;
}

1.19 文件属性操作函数

webserver笔记1-3_第31张图片

/*
#include 
    //查看是否有权限,查看文件是否存在
    int access(const char *pathname, int mode);
    -mode:  R_OK: 是否有读权限
            W_OK: 是否有写权限
            X_OK: 是否有读写权限
            F_OK: 文件是否存在
    返回值: 成功返回0,失败返回-1   设置errno
#include 
    //修改文件权限
    int chmod(const char *pathname, mode_t mode);
    -mode: 需要修改的权限的值,8进制的数
    返回值:成功返回0,失败返回-1  设置errno
#include 
    //修改所有者
    int chown(const char *pathname, uid_t owner, gid_t group);
    -owner:所有者id    vim /etc/passwd     id liukunpeng
    -group:组id        vim /etc/group
#include 
#include 
    //缩减或扩展到指定的长度
    int truncate(const char *path, off_t length);
    缩减把多余的删除        扩展从后面添加
*/
#include 
#include 
#include 
#include 
int main(){
    int ret = access("a.txt", F_OK);
    if(ret == -1){
        perror("exist");
        return -1;
    }
    printf("文件存在\n");
    int res = chmod("a.txt", 0775);
    if(ret == -1){
        perror("changeMode");
        return -1;
    }
    int r =  truncate("a.txt", 1000);
    if(ret == -1){
        perror("changeSize");
        return -1;
    }
    return 0;
}

1.20 目录操作函数

webserver笔记1-3_第32张图片

/*
#include 
#include 
    //创建文件夹
    int mkdir(const char *pathname, mode_t mode);
#include 
    //删除空文件夹
    int rmdir(const char *pathname);
#include 
    //移动文件夹
    int rename(const char *oldpath, const char *newpath);
#include 
    //修改进程的工作目录
    int chdir(const char *path);
#include 
    //获取当前工作路径
    char *getcwd(char *buf, size_t size);
    -buf: 存储的路径,指向的是一个数组
    -size: 数组的大小
    返回值:第一个参数的地址
*/

1.21 目录遍历函数

webserver笔记1-3_第33张图片

webserver笔记1-3_第34张图片

/*
#include 
#include 
    //打开一个目录
    DIR *opendir(const char *name);
    返回值:目录流信息  失败返回NULL
#include 
    //读取目录
    struct dirent *readdir(DIR *dirp);
    -dirp: 返回的结果
    返回值:是一个结构体    到达末尾返回NULL且errno不会改变  读取失败返回NULL且errno会设置
#include 
#include 
    //关闭目录
    int closedir(DIR *dirp);
*/
#include 
#include 
#include 
#include 
#include 

int getFileNum(const char *path){
    DIR *dir = opendir(path);
    if(dir == NULL){
        perror("opendir");
        exit(0);
    }
    int total = 0;
    struct dirent *ptr;
    while (ptr = readdir(dir))
    {
        char * dname = ptr->d_name;
        if(strcmp(dname, "./") == 0 || strcmp(dname, "../") == 0){
            continue;
        }
        //判断是否是普通文件
        if(ptr->d_type == DT_DIR){
            //继续读取目录
            char newpath[256];
            sprintf(newpath, "%s/%s", path, dname);
            total += getFileNum(newpath);
        }
        if(ptr->d_type == DT_REG){
            total++;
        }
    }
    closedir(dir);
    return total;
}
int main(int argc, char * argv[]){
    //读取某个目录下的所有普通文件的个数
    if(argc < 2){
        printf("%s path\n",argv[0]);
        return -1;
    }
    printf("%s\n",argv[1]);
    int total = getFileNum(argv[1]);
    printf("文件个数%d\n", total);
    // opendir()
    // readdir()
    return 0;
}

1.22 dup、dup2函数

/*
#include 
    //复制一个新的文件描述符。从空闲的文件描述符中找一个最小的作为新的文件描述符    
    一个文件可以对应多个文件描述符
    int dup(int oldfd);
    -oldfd: 旧的文件描述符
    //重定向文件描述符。
    int dup2(int oldfd, int newfd);
    oldfd指向 a.txt,newfd指向 b.txt,经过dup2函数
    关闭 b.txt并把 newfd 指向 a.txt 
    oldfd必须是有效的文件描述符。newfd和oldfd相同时相当于什么也没做
*/
#include 
#include 
#include 
#include 
#include 
#include 

int main(){
    int fd = open("a.txt", O_RDWR | O_CREAT, 0664);
    if(fd == -1){
        perror("open");
        return -1;
    }
    int fd1 = dup(fd);  
    printf("fd:%d;\tfd1:%d\n", fd, fd1);
    char * str = "hello world!";
    int res = write(fd1, str, strlen(str));   //通过新文件描述符向文件写入
    if(res == -1){
        perror("write");
        return -1;
    }
    int fd2 = open("b.txt", O_RDWR | O_CREAT, 0664);
    if(fd2 == -1){
        perror("open");
        return -1;
    }
    dup2(fd, fd2);
    int ret = write(fd2, str, strlen(str));   //通过新文件描述符向文件写入
    if(ret == -1){
        perror("write");
        return -1;
    }
    
    close(fd);
    close(fd1);
    close(fd2);
    return 0;
}

1.23 fcntl函数

webserver笔记1-3_第35张图片

/*
#include 
#include 
    //复制文件描述符。获取文件状态标志
    int fcntl(int fd, int cmd, ... arg  );
    -cmd: 需要对文件描述符做的操作
        -F_DUPFD:复制文件描述符
        -F_GETFL: 获取指定的文件描述符的文件状态flag和open的flag一样
        必选项:O_RDONLY  O_WRONLY  O_RDWR
        可选项:O_APPEND  NONBLOCK
        O_APPEND:追加数据  NONBLOCK:设置成非阻塞
阻塞和非阻塞:阻塞是调用函数结果返回之前当前的进程或线程被挂起
*/
#include 
#include 
#include 
#include 
int main(){
    //复制文件描述符
    int fd = open("a.txt", O_RDWR);
    if(fd == -1){
        perror("open");
        return -1;
    }
    // fcntl(fd, F_DUPFD);
    int flag = fcntl(fd, F_GETFL);
    flag |= O_APPEND;
    int ret =  fcntl(fd, F_SETFL, flag);
    if(ret == -1){
        perror("fcntl");
        return -1;
    }
    char *str = "你好";
    write(fd, str, strlen(str));
    close(fd);
    return 0;
}

第2章 Linux多进程开发

2.1 进程概述

程序和进程

webserver笔记1-3_第36张图片

webserver笔记1-3_第37张图片

单道、多道程序设计

webserver笔记1-3_第38张图片

时间片

webserver笔记1-3_第39张图片

并行和并发

webserver笔记1-3_第40张图片

进程控制块(PCB)

webserver笔记1-3_第41张图片

webserver笔记1-3_第42张图片

进程状态

webserver笔记1-3_第43张图片
webserver笔记1-3_第44张图片

查看进程

ps aux(或ajx) 
-a:显示终端上的所有进程,包括其他用户的进程
-u:显示进程的详细信息
-x:显示没有控制终端的进程
-j:列出与作业控制相关的信息

webserver笔记1-3_第45张图片

top

进程号和相关函数

webserver笔记1-3_第46张图片

进程创建

webserver笔记1-3_第47张图片

/*
#include 
#include 

    pid_t fork(void);
*/

#include 
#include 
#include 
int main(){
    pid_t pid =  fork();
    //
    if(pid > 0){
        //如果大于0,返回的是创建的子进程的进程号
        printf("i am parent process, pid: %d, ppid: %d\n", getpid(), getppid());
    }else if(pid == 0){
        //当前是子进程
        printf("i am child process, pid: %d, ppid: %d\n", getpid(), getppid());
    }
    for(int i = 0; i < 3; i++){
        printf("i: %d, pid: %d\n", i, getpid());
        sleep(1);
    }
    return 0;
}

父子进程虚拟进程空间

父子进程代码段都是一样的,只是执行不一样,修改其中一个进程的变量不会影响另一个进程的变量

webserver笔记1-3_第48张图片

读时共享、写时拷贝的原理:

Linux 的 fork() 使用是通过写时拷贝实现。写时拷贝是一种可以推迟甚至避免拷贝数据的技术。内核并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间。只有在写入时才会复制地址空间(重新开辟一块内存),从而使各个进程拥有自己的地址空间。即资源的复制只有在写入时才会进行,在此之前,只有以只读的方式进行。

fork() 之后的父子进程共享文件,此时的 fork() 产生的子进程与父进程相同的文件描述符指向相同的文件表,引用计数增加,共享文件偏移指针。

父子进程关系及GDB调试

webserver笔记1-3_第49张图片

父子进程之间的关系:

区别

  1. fork()函数的返回值不同

    • 父进程中:>0 返回子进程的ID

    • 子进程中:=0

  2. 内核区进程控制块中的数据

    • 当前的进程id pid

    • 当前的进程的父进程的id ppid

    • 信号集

共同点

  1. 某些状态下:子进程刚被创建出来,还没有进行任何的写操作
    • 用户区的数据
    • 文件描述符

父子进程对变量是不是共享的?

  • 刚开始是共享的,修改了数据就不共享了。读时共享,写时拷贝
follow-fork-mode  #查看调试跟踪状态
set follow-fork-mdoe [parent(默认) | child]   #修改调试进程
set detack-on-fork [on | off]	#默认为on,表示调试当前的进程时,其他进程继续运行,
#如果是off,调试当前进程时,其他进程被GDB挂起
info inferiors	#查看调试的进程
inferior id		#切换调试的进程
detack inferiors id		#使进程脱离GDB调试

2.2 exec函数族

函数族介绍

webserver笔记1-3_第50张图片

使用fork()函数复制父进程,然后在子进程中运行可执行程序,替换掉原来的用户区数据,只保留了内核区部分。

webserver笔记1-3_第51张图片

/*
#include 
extern char **environ;
int execl(const char *path, const char *arg, ...);
    - path: 需要指定的执行的文件的路径
    - arg: 可执行文件所需要的参数
        第一个参数一般没有什么作用,为了方便一般写上执行程序的名称
        第二个开始,是程序执行所需要的参数,并且以NULL结尾
    返回值:只有出错时才有返回值 -1,且设置errno。成功没有返回值
int execlp(const char *file, const char *arg, ...);
    -会到环境变量中查找指定的可执行文件,如果找到了就执行
*/
#include 
#include 
int main(){

    pid_t pid = fork();
    if(pid > 0){
        //父进程
        printf("我是父进程,pid是:%d\n", getpid());
        sleep(1);
    }else if(pid == 0){
        // execl("hello", "hello", NULL);       //执行文件
        //execl("/bin/ps", "ps", "aux", NULL);    //执行shell命令
        execlp("ps", "ps", "aux", NULL);
        printf("我是子进程,pid是:%d\n", getpid());
    }
    for(int i = 0; i < 3; i++){
        printf("i = %d, pid = %d\n", i, getpid());
    }
}

2.3 进程控制

进程退出

webserver笔记1-3_第52张图片

/*
#include   //c库函数
    void exit(int status);  
    - status:进程退出时的一个状态信息。父进程回收子进程资源的时候可以获得
#include  //linux库函数
    void _exit(int status);
*/
#include 
#include 
#include 
int main(){

    printf("hello\n");
    printf("world");
   //printf自带缓冲区,当出现\n时刷新缓冲区并输出到屏幕,调用exit()时会自动刷新缓冲区,_exit()不会刷新
    // exit(0);     	//输出hello world
    // _exit(0);		//输出hello
    return 0;
}

孤儿进程

webserver笔记1-3_第53张图片

#include 
#include 
#include 
int main(){
    pid_t pid =  fork();
    //
    if(pid > 0){
        //如果大于0,返回的是创建的子进程的进程号
        printf("i am parent process, pid: %d, ppid: %d\n", getpid(), getppid());
    }else if(pid == 0){
        //当前是子进程
        sleep(10);		//等主进程结束,子进程ppid变成1
        printf("i am child process, pid: %d, ppid: %d\n", getpid(), getppid());
        
    }
    for(int i = 0; i < 3; i++){
        printf("i: %d, pid: %d\n", i, getpid());
        sleep(1);
    }
    return 0;
}

僵尸进程

webserver笔记1-3_第54张图片

2.4 进程回收

wait

webserver笔记1-3_第55张图片

/*
#include 
#include 
    pid_t wait(int *wstatus);   //等待任意一个子进程结束,回收子进程资源
    - int *wstatus: 进程退出的状态信息,传出参数
    返回值:成功返回终止的进程id,失败返回-1
    pid_t waitpid(pid_t pid, int *wstatus, int options);    //
调用wait()函数的进程会被挂起(阻塞),直到他的一个子进程退出或收到不能被忽略的信号才被唤醒
如果没有子进程了函数立刻返回-1,如果子进程都已经结束也会立刻返回-1
*/

#include 
#include 
#include 
#include 
#include 
int main(){
    pid_t pid;
    for(int i = 0; i < 5; i++){
        pid = fork();
        if(pid == 0) break;
    }
    if(pid > 0){
        while(1){
            printf("i am parent, pid = %d\n", getpid());
            int st;
            int ret = wait(&st);
            if(ret == -1) break;
            printf("child die, pid=%d\n", ret);
            if(WIFEXITED(st)){
                printf("推出的状态码%d\n",WEXITSTATUS(st));
            }else if(WIFSIGNALED(st)){
                printf("被%d号信号干掉了\n", WTERMSIG(st));
            }
            sleep(1);
        }
    }else if(pid == 0){
        while(1){
            printf("child,pid= %d\n", getpid());
            sleep(1);
        }
    }
    exit(0);
    // return 0;
}

webserver笔记1-3_第56张图片

waitpid

/*
    pid_t waitpid(pid_t pid, int *wstatus, int options);    //回收指定进程号的子进程,可以设置是否阻塞
    - int *wstatus: 进程退出的状态信息,传出参数
    - pid_t pid:<-1: 回收指定进程组内子进程,组id为pid绝对值
                =-1: 回收所有子进程。相当于wait
                =0: 回收当前进程组的所有子进程
                >0: 回收指定子进程的id,pid
    - int options: 设置阻塞或非阻塞,0:阻塞。WNOHANG:非阻塞
    返回值:>0:返回子进程的id。=0:options=WNOHANG时,表示还有子进程或者。 >0:返回终止进程的id
*/

2.5 进程间通信

概念

webserver笔记1-3_第57张图片

通信方式

webserver笔记1-3_第58张图片

匿名管道

webserver笔记1-3_第59张图片

ls | wc -l  # '|' 管道符
管道的特点

webserver笔记1-3_第60张图片

webserver笔记1-3_第61张图片

为什么只能在有关系的进程间使用

因为子进程与父进程共享文件描述符,对应一个管道

webserver笔记1-3_第62张图片

管道的数据结构

webserver笔记1-3_第63张图片

环形队列,读过的可以直接继续写

管道的使用

webserver笔记1-3_第64张图片

/*
#include 
    int pipe(int pipefd[2]);//创建一个匿名管道,进行进程间通信
    - int pipefd[2]:传出参数,传出管道的两个文件描述符。pipefd[0]对应管道的读端,pipefd[1]对应管道的写端
    返回值:成功返回0,失败返回-1并设置errno
    匿名管道只能用于有关系的进程之间通信
    管道默认是阻塞的,如果管道中没有数据,read阻塞,如果管道满了,write阻塞
    双向通信可能会读到自己写的内容--读的端把写端close,写的端把读端close
*/

#include 
#include 
#include 
#include 
int main(){     //子进程向父进程发送数据
    //在fork之前创建管道
    int pipefd[2];
    int ret = pipe(pipefd);
    if(ret == -1){
        perror("pipe");
        exit(-1);
    }
    pid_t pid = fork();
    if(pid > 0){
        //父进程
        printf("i am parent,pid:%d\n", getpid());
        char buff[1024] = {0};
        while (1)
        {
            read(pipefd[0], buff, sizeof(buff));
            printf("parent received:'%s',pid: %d\n",buff, getpid());

            write(pipefd[1], "hello,i am parent", 17);
            sleep(1);		
        }
    }else if(pid == 0){
        //子进程
        char buff[1024] = {0};
        printf("i am child,pid: %d\n", getpid());
        while(1){
            write(pipefd[1], "hello,i am child", 16);
            sleep(1);

            read(pipefd[0], buff, sizeof(buff));
            printf("child received:'%s',pid: %d\n",buff, getpid());
        }
    }else if(pid < 0){
        //创建失败
        perror("fork");
        exit(-1);
    }

    return 0;
}
匿名管道通信案例
/*
    实现ps aux | grep xxx
    ps aux,子进程结束后,将数据发送到父进程
    父进程接收到数据,过滤
    pipe()
    execlp()
    子进程将标准输出重定向到管道的写端
*/

#include 
#include 
#include 
#include 
#include 
int main(){
    int pipefd[2];
    int ret = pipe(pipefd);
    if(ret == -1){
        perror("pipe");
        exit(-1);
    }
    pid_t pid = fork();
    if(pid > 0){
        //父进程
        close(pipefd[1]);
        char buf[1024] = {0};
        int len = -1;
        while(len = read(pipefd[0], buf, sizeof(buf) - 1)){
            printf("%s\n", buf);
        }
    wait(NULL);
    }else if(pid == 0){
        //子进程
        //执行ps aux 文件描述符重定向到
        dup2(pipefd[1], STDOUT_FILENO);
        execlp("ps", "ps", "aux", NULL);

    }
    return 0;
}
管道的读写特点和管道设置为非阻塞

管道的读写特点:

使用管道时,需要注意以下几种情况(假设都是阻塞I/O操作)

  1. 所有的指向管道写端的文件描述符都关闭了(管道写端引用计数为0),有进程从管道读端读数据,那么管道中有剩余数据被读取以后,再次去读数据会返回0。就像读到文件末尾

  2. 如果有指向管道写端的文件描述符没有关闭(管道写端引用计数大于0),管道写端也没有往管道中写数据,有进程从管道中读数据,剩余的数据被读取后,再次读取会阻塞,直到管道中有数据可以读才读取数据并返回

  3. 读端都关闭了,有进程向管道中写数据,该进程会受到一个信号SIGPIPE,通常会导致进程异常处理

  4. 有指向读端的没有关闭,但是没有从管道中读数据,有进程往管道中写数据,那么管道背写满时,再次写会阻塞。直到管道中有空位置

    /*设置非阻塞
        int flags = fcntl(fd[0], F_GETFL)   //获取原来的flag
        flags |= O_NONBLOCK;            //修改flag
        fcntl(fd[0], F_SETFL, flags)    //设置新的flag
    */
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    int main(){     //子进程向父进程发送数据
        //在fork之前创建管道
        int pipefd[2];
        int ret = pipe(pipefd);
        if(ret == -1){
            perror("pipe");
            exit(-1);
        }
        pid_t pid = fork();
        if(pid > 0){
            //父进程
            close(pipefd[1]);
            //修改为非阻塞
            int flags = fcntl(pipefd[0], F_GETFL);
            flags |= O_NONBLOCK;
            fcntl(pipefd[0], F_SETFL, flags);
    
            printf("i am parent,pid:%d\n", getpid());
            char buff[1024] = {0};
            while (1)
            {
                int len = read(pipefd[0], buff, sizeof(buff));
                printf("len:%d\n",len);
                printf("parent received:'%s',pid: %d\n",buff, getpid());
                memset(buff, 0, 1024);
                sleep(2);
            }
        }else if(pid == 0){
            //子进程
            close(pipefd[0]);
            printf("i am child,pid: %d\n", getpid());
            while(1){
                write(pipefd[1], "hello,i am child", 16);
                sleep(10);
            }
        }else if(pid < 0){
            //创建失败
            perror("fork");
            exit(-1);
        }
    
        return 0;
    }
    
    

有名管道

webserver笔记1-3_第65张图片

webserver笔记1-3_第66张图片

使用

webserver笔记1-3_第67张图片

//写数据write.c
/*
    创建fifo
    命令:mkfifo
#include 
#include 

    int mkfifo(const char *pathname, mode_t mode);
    - pathname:管道名称路径
    - mode:文件权限,和open里的一样
    返回值:成功返回0失败返回-1并设置errno
*/
#include 
#include 
#include 
#include 
#include 
#include 
#include 
int main(){
    int res = access("test", F_OK);
    if(res == -1){
        printf("管道不存在,创建管道\n");
        int ret = mkfifo("test", 0664);
        if(ret == -1){
            perror("mkfifo");
            exit(0);
        }
    }
    //以写的方式打开管道
    int fd = open("test", O_WRONLY);
    if(fd == -1){
        perror("open");
        exit(-1);
    }
    //写数据
    for(int i = 0; i < 100; i++){
        char buf[1024] = {0};
        sprintf(buf, "hello,%d\n",i);
        printf("write data: %s\n", buf);
        write(fd, buf, strlen(buf));
        sleep(1);
    }
    close(fd);
    return 0;
}
//读数据read.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main(){
    //1.打开管道文件
    int fd  = open("test", O_RDONLY);
    if(fd == -1){
        perror("open");
        exit(0);
    }
    //读数据
    while(1){
        char buf[1024] = {0};
        int len = read(fd, buf, sizeof(buf));
        if(len == 0){
            printf("写端断开连接\n");
            break;
        }
        printf("read buf:%s\n", buf);
    }
    close(fd);
    return 0;
}

有名管道注意事项:

  1. 只读状态打开管道会阻塞,直到另一个进程打开写的管道
  2. 只写状态打开管道会阻塞,直到另一个进程打开读的管道

读管道:

  • 管道有数据,read返回实际的字节数
  • 管道无数据:
    • 管道写端被关闭,read返回0(相当于读到文件末尾)
    • 写端没有被关闭,read阻塞等待

写管道:

  • 读端被关闭,进程异常终止(收到一个SIGPIPE信号)
  • 读端没有关闭:
    • 管道已经满了,write会阻塞
    • 没满,写入数据并返回写入的字节数
有名管道实例

a发送b接收;b发送a接收;

#include 
#include 
#include 
#include 
#include 
#include 
#include 
int main(){
    int res = access("chatA", F_OK);
    if(res == -1){
        printf("管道不存在,创建管道\n");
        int ret = mkfifo("chatA", 0664);
        if(ret == -1){
            perror("mkfifo");
            exit(-1);
        }
    }
    int res1 = access("chatB", F_OK);
    if(res1 == -1){
        printf("管道不存在,创建管道\n");
        int ret = mkfifo("chatB", 0664);
        if(ret == -1){
            perror("mkfifo");
            exit(-1);
        }
    }
    //以写的方式打开管道
    int fdw = open("chatA", O_WRONLY);
    if(fdw == -1){
        perror("open");
        exit(-1);
    }
    printf("打开chatA写管道成功,等待写入......\n");
    //以读的方式打开管道
    int fdr = open("chatB", O_RDONLY);
    if(fdr == -1){
        perror("open");
        exit(-1);
    }
    printf("打开chatB读管道成功,等待读取......\n");
    //写读数据
    char buf[128];
    while(1){
        memset(buf, 0, 128);
        fgets(buf, 128, stdin);
        int ret = write(fdw, buf, strlen(buf));
        if(ret == -1){
            perror("write");
            break;
        }
        memset(buf, 0, 128);
        int len = read(fdr, buf, 128);
        if(len <= 0){
            perror("read");
            break;
        }
        printf("%s\n", buf);
    }
    close(fdr);
    close(fdw);
    return 0;
}
#include 
#include 
#include 
#include 
#include 
#include 
#include 
int main(){
    int res = access("chatA", F_OK);
    if(res == -1){
        printf("管道不存在,创建管道\n");
        int ret = mkfifo("chatA", 0664);
        if(ret == -1){
            perror("mkfifo");
            exit(-1);
        }
    }
    int res1 = access("chatB", F_OK);
    if(res1 == -1){
        printf("管道不存在,创建管道\n");
        int ret = mkfifo("chatB", 0664);
        if(ret == -1){
            perror("mkfifo");
            exit(-1);
        }
    }
    //以读的方式打开管道
    int fdr = open("chatA", O_RDONLY);
    if(fdr == -1){
        perror("open");
        exit(-1);
    }
    printf("打开chatA读管道成功\n");
    //以写的方式打开管道
    int fdw = open("chatB", O_WRONLY);
    if(fdw == -1){
        perror("open");
        exit(-1);
    }
    printf("打开chatB写管道成功\n");

    //写数据
    char buf[128];
    while(1){
        memset(buf, 0, 128);
        int len = read(fdr, buf, 128);
        if(len <= 0){
            perror("read");
            break;
        }
        printf("%s\n", buf);

        memset(buf, 0, 128);
        fgets(buf, 128, stdin);
        int ret = write(fdw, buf, strlen(buf));
        if(ret == -1){
            perror("write");
            break;
        }
    }
    close(fdr);
    close(fdw);
    return 0;
}

2.6 内存映射

概述

webserver笔记1-3_第68张图片

webserver笔记1-3_第69张图片

进程间通信

/*
#include 
    创建一个新的映射在虚拟地址空间
    void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
    -void *addr:映射的首地址,null由内核指定,映射的地址通过返回值返回
    -size_t length: 要映射的数据的长度,这个值不能为0,一般用文件的长度。获取文件长度用lseek或stat
    -int prot:对申请内存映射区的操作权限,必须要有读权限
        PROT_EXEC  可执行权限

        PROT_READ  读权限.

        PROT_WRITE 写权限.

        PROT_NONE  没有权限.
    -int flags:
        MAP_SHARED:映射区的数据会和磁盘文件进行同步
        MAP_PRIVATE:不同步,内存中数据改变,文件不会改变,而是会重新创建一个新的文件
    -int fd:需要映射的文件描述符,open获得的权限不能和prot冲突
    -off_t offset:映射时的偏移量,必须是4k的整数倍,0表示从文件开头
    返回值:成功返回映射的内存的首地址,失败返回 MAP_FAILED((void*)-1)
    int munmap(void *addr, size_t length);
    释放内存映射
    -void *addr:需要释放的内存地址
    -size_t length:需要释放的内存大小。等于length
*/
/*
    使用内存映射实现进程间通信
    1.有关系的进程
        -还没有子进程的时候,通过父进程先创建一个内存映射区
        -有了内存映射区后,创建子进程
        -父子进程共享创建的内存映射区
    2.没有关系的进程
        -准备一个大小不是0的磁盘文件
        -进程1创建内存映射区
        -进程2创建内存映射区
        -使用内存映射区通信
    内存映射区通信不会阻塞
*/
#include    //mmap
#include       //perror
#include      //exit
#include   //pid_t     wait
#include      //lseek
#include       //open
#include      //strcpy
#include    //wait
int main(){

    int fd = open("test.txt", O_RDWR);
    int size = lseek(fd, 0, SEEK_END);
    void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if(ptr == MAP_FAILED){
        perror("mmap");
        exit(-1);
    }
    pid_t pid = fork();
    if(pid > 0){
        //父进程
        wait(NULL);
        char buf[64];
        strcpy(buf, (char *)ptr);		//void* 转 char*
        printf("read data: %s\n", buf);
        
    }else if(pid == 0){
        //子进程
        strcpy((char*)ptr, "nihao a, son!!!");
    }else{
        perror("fork");
        exit(-1);
    }
    munmap(ptr, size);
    return 0;
}
  1. 如果对mmap的返回值(ptr)做++操作(ptr++), munmap是 否能够成功?
    • 可对其进行ptr++操作,释放的时候要释放原来的地址
  2. 如果open时0_ RDONLY, mmap时prot参 数指定PROT_READ | PROT_WRITE会怎样?
    • 错误,返回MAP_FAILED
  3. 如果文件偏移量为1000会怎样?
    • 偏移量必须是4k的整数倍,1000会返回MAP_FAILED
  4. mmap什么情况下会调用失败?
    • length = 0时;prot只指定了写权限没有读权限,与open冲突
  5. 可以open的时候0_ CREAT一个新文件来创建映射区吗?
    • 可以,但是创建的文件大小为0时不可以,可以对新的文件进行扩展利用lseek或truncate
  6. mmap后关闭文件描述符,对mmap映射有没有影响?
    • 关闭文件描述符后映射区仍然存在,不会产生影响
  7. 对ptr越界操作会怎样?
    • 越界操作的是非法内存产生段错误

文件拷贝

/* 不能对大文件拷贝
对原始文件进行内存映射
创建一个新的文件
把新文件的数据映射到内存
通过内存拷贝将第一个文件的内存数据拷贝到新的文件中
*/

#include    //mmap
#include       //perror
#include      //exit
#include   //pid_t     wait
#include      //lseek
#include       //open
#include      //strcpy
#include    //wait

int main(){
    int fd = open("test.txt", O_RDWR);
    int fdnew = open("cpy.txt", O_RDWR|O_CREAT, 0664);
    int len = lseek(fd, 0, SEEK_END);
    truncate("cpy.txt", len);
    write(fdnew, " ", 1);
    void *ptr1 = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    void *ptr2 = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fdnew, 0);
    if(ptr1 == MAP_FAILED || ptr2 == MAP_FAILED){
        perror("mmap");
        exit(-1);
    }
    memcpy(ptr2, ptr1, len);
    munmap(ptr2, len);
    munmap(ptr1, len);
    close(fdnew);
    close(fd);
    return 0;
}

匿名映射

/*
    匿名映射:没有文件实体
    flags:
*/
#include    //mmap
#include       //perror
#include      //exit
#include   //pid_t     wait
#include      //lseek
#include       //open
#include      //strcpy
#include    //wait
int main(){
    int len = 4096;
    void *ptr = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
    pid_t pid = fork();
    if(pid > 0){
        //父进程
        strcpy((char*)ptr, "hello,world");
        wait(NULL);

    }else if(pid == 0){
        //子进程
        sleep(1);
        printf("%s\n", (char*)ptr);
    }
    munmap(ptr, len);
    return 0;
}

2.7 信号概述

概述

webserver笔记1-3_第70张图片

webserver笔记1-3_第71张图片

kill -l		//62个
1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

webserver笔记1-3_第72张图片

webserver笔记1-3_第73张图片
webserver笔记1-3_第74张图片

webserver笔记1-3_第75张图片

kill、raise、abort函数

/*
#include 
#include 
    //向进程发送信号
    int kill(pid_t pid, int sig);
    -pid:需要发送给指定的进程      >0:将信号发送给指定的进程
                                  =0: 将信号发送给当前的进程组
                                  =-1:将信号发送给每一个有权限接收到这个信号的进程
                                  <-1:发送给某个进程组的id取反
    -sig:发送的信号,为0时表示不发送任何信号

#include 
    //给当前进程发送信号
    int raise(int sig);     =kill(getpid(), sig)
    返回值:成功返回0,失败返回非0

#include 
    //发送SIGABRT信号给当前进程,杀死当前进程
    void abort(void);       =kill(getpid(), SIGABRT)
*/
#include 
#include 
#include 
#include 
int main(){
    pid_t pid = fork();
    if(pid > 0){
        //父进程
        printf("parent process\n");
        sleep(2);
        printf("kill process\n");
        kill(pid, SIGABRT);
    }else if(pid == 0){
        //子进程
        for(int i = 0; i < 5; i++){
            printf("child process\n");
            sleep(1);
        }
    }
    return 0;
}

alarm函数

/*
#include 
    //函数调用开始倒计时,当计时结束函数会给当前进程发送一个信号:SIGALARM
    unsigned int alarm(unsigned int seconds);
    返回值:倒计时剩余的时间
	定时器与进程的状态无关
    SIGALARM:默认终止进程,每一个进程都有唯一的定时器
*/
#include 
#include 
#include 
#include 
int main(){
    //1秒钟电脑能数多少个数
    //实际的时间 = 内核时间 + 用户时间 + 消耗的时间
    alarm(1);
    int i = 0;
    while(1){
        printf("%d\n", i++);
    }
    return 0;
}

setitimer定时器

/*
#include 
    //设置定时器,可以替代alarm,计时精度比alarm高可以达到微秒 us,可以实现周期性的
    int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
    -int which: 定时器以什么时间计时
        TIMER_REAL:真实时间,时间到达发送SIGALARM
        ITIMER_VIRTUAL: 用户时间,到达发送SIGVTALRM信号
        ITIMER_PROF:以该进程在用户态和内核态下所消耗的时间来计时,时间到达,发送SIGPROF

    -const struct itimerval *new_value: 设置定时器的属性
            过10秒后,每隔2秒定时一次
            struct itimerval {          //定时器的结构体
               struct timeval it_interval;      //每个阶段的时间,间隔时间  ---2秒
               struct timeval it_value;     //延迟多长时间执行 ---10秒
            };

            struct timeval {
               time_t      tv_sec;       //秒数
               suseconds_t tv_usec;     //微秒
            };

    -struct itimerval *old_value: 记录上一次的定时时间参数,一般不使用指定NULL

    返回值:成功返回0,失败返回-1并设置errno
*/
#include 
#include 
#include 
#include 
#include 
#include 
//过3秒后每隔2秒定时一次
int main(){

    struct itimerval new_value;
    //设置间隔时间
    new_value.it_interval.tv_sec = 2;
    new_value.it_interval.tv_usec = 0;
    //设置延迟时间
    new_value.it_value.tv_sec = 3;
    new_value.it_value.tv_usec = 0;

    int ret = setitimer(ITIMER_REAL, &new_value, NULL);
    printf("定时器开始了\n");		//定时器是非阻塞的
    if(ret == -1){
        perror("setitimer");
        exit(-1);
    }

    getchar();
    return 0;
}

信号捕捉函数

/*
#include 
    SIGKILL  SIGSTOP不能被捕捉,不能被忽略
    typedef void (*sighandler_t)(int);
    //设置某个信号的捕捉信号
    sighandler_t signal(int signum, sighandler_t handler);
        -signum:要捕捉的信号
        -handler:捕捉的信号要如何处理
            -SIG_IGN: 忽略信号
            -SIG_DFL: 使用信号的默认行为
            -回调函数:这个函数是由内核调用,捕捉到信号如何处理
        返回值:成功返回上一次注册的信号处理函数的地址,第一次返回NULL
                失败返回SIG_ERR并设置errno
    回调函数:
        -需要程序员实现,提前准备好的,函数的类型根据实际需求,看函数的指针的定义
        -不是程序员调用,而是当信号产生由内核调用
        -函数指针是实现回调的手段,函数实现之后,将函数名放到函数指针的位置
*/
#include 
#include 
#include 
#include 
#include 
#include 
//过3秒后每隔2秒定时一次
void myalarm(int num){
    printf("捕捉到信号%d\n", num);
    printf("xxxx\n");
}
int main(){
    //注册信号捕捉
    signal(SIGALRM, myalarm);

    struct itimerval new_value;
    //设置间隔时间
    new_value.it_interval.tv_sec = 2;
    new_value.it_interval.tv_usec = 0;
    //设置延迟时间
    new_value.it_value.tv_sec = 3;
    new_value.it_value.tv_usec = 0;

    int ret = setitimer(ITIMER_REAL, &new_value, NULL);
    printf("定时器开始了\n");
    if(ret == -1){
        perror("setitimer");
        exit(-1);
    }

    getchar();
    return 0;
}

信号集及相关函数

webserver笔记1-3_第76张图片

webserver笔记1-3_第77张图片

有未决信号,未决信号集第二位变成1,对比阻塞信号集的同一信号的第二位是否为1(阻塞信号集默认为0),如果为1就阻塞

/*
 #include 
    //清空信号集中的数据,将信号集中的所有标志位置为0,
    int sigemptyset(sigset_t *set);
        -set传出参数,需要操作的信号集
        返回值:成功返回0,失败返回-1
    //将信号集中的所有标志位置为1,
    int sigfillset(sigset_t *set);
    //将信号集中的某一信号对应的标志位设为1,表示阻塞这个信号
    int sigaddset(sigset_t *set, int signum);
    //将信号集中的某一信号对应的标志位设为0,表示不阻塞这个信号
    int sigdelset(sigset_t *set, int signum);
    //判断某个信号是否阻塞
    int sigismember(const sigset_t *set, int signum);
    返回值:被阻塞返回1,不阻塞返回0,失败返回-1;
*/
#include 
#include 
#include 
#include 
#include 
#include 
int main(){
    //创建信号集
    sigset_t sigset;
    //清空信号集
    sigemptyset(&sigset);
    //判断SIGINT在不在信号集中
    int ret = sigismember(&sigset, SIGINT);
    if(ret == 0){
        printf("SIGINT不阻塞\n");
    }else if(ret == 1){
        printf("SIGINT阻塞的\n");
    }
    //添加几个信号到信号集中
    sigaddset(&sigset, SIGINT);
    sigaddset(&sigset, SIGQUIT);
    int res1 = sigismember(&sigset, SIGINT);
    if(res1 == 0){
        printf("SIGINT不阻塞\n");
    }else if(res1 == 1){
        printf("SIGINT阻塞的\n");
    }
    int res2 = sigismember(&sigset, SIGQUIT);
    if(res2 == 0){
        printf("SIGQUIT不阻塞\n");
    }else if(res2 == 1){
        printf("SIGQUIT阻塞的\n");
    }
    //删除一个信号
    printf("删除信号....\n");
    sigdelset(&sigset, SIGQUIT);
    int res3 = sigismember(&sigset, SIGQUIT);
    if(res3 == 0){
        printf("SIGQUIT不阻塞\n");
    }else if(res3 == 1){
        printf("SIGQUIT阻塞的\n");
    }
    return 0;
}

sigprocmask函数

/*
#include 
    //将自定义信号集中的数据设置到内核中(设置阻塞,解除阻塞,替换)
    int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
        -how:如何对内核阻塞信号集进行处理(mask是内核默认阻塞信号集)
            -SIG_BLOCK:将用户设置的阻塞信号集添加到内核中,内核中原来的数据不变    mask | set
            -SIG_UNBLOCK:根据用户设置的数据,对内核中的数据进行解除阻塞    mask &= !set
            -SIG_SETMASK:覆盖内核中原来的
        -set:已经初始化好的用户定义的信号集
        -oldset:保存设置之前的内核中的阻塞信号集的状态,可以是NULL
        返回值:成功返回0,失败返回-1设置errno
    //获取内核中的未决信号集
    int sigpending(sigset_t *set);
        -set:传出参数,保存的是内核中未决信号集中的信息
*/
#include 
#include 
#include 
#include 
#include 
#include 
//把所有的常规信号的未决状态打印到屏幕
int main(){
    sigset_t sigset;
    sigemptyset(&sigset);
    //设置2、3号信号阻塞
    sigaddset(&sigset, SIGINT);
    sigaddset(&sigset, SIGQUIT);
    //修改内核中的阻塞信号集
    sigprocmask(SIG_BLOCK, &sigset, NULL);
    int num = 0;
    while(1){
        sigset_t set;
        sigemptyset(&set);
        sigpending(&set);
        //遍历
        for(int i = 1; i <= 32; i++){
            if(sigismember(&set, i) == 1){
                printf("1");
            }else if(sigismember(&set, i) == 0){
                printf("0");
            }
        }
        printf("\n");
        sleep(1);
        num++;
        if(num == 10){
            sigprocmask(SIG_UNBLOCK, &sigset, NULL);
        }
    }
    return 0;
}

sigaction信号捕捉函数

/*
#include 
    //信号捕捉
    int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
        -signum:需要捕捉的信号的编号,或者信号的宏值
        -act:捕捉到信号之后的处理动作
        -oldact:上一次对这个信号捕捉使用的设置
        返回值:成功返回0,失败返回-1。
        struct sigaction {
               void     (*sa_handler)(int);                         //函数指针,信号捕捉之后处理的函数
               void     (*sa_sigaction)(int, siginfo_t *, void *);  //不常用
               sigset_t   sa_mask;                                  //临时阻塞信号集,在信号捕捉函数执行过程中,临时阻塞某些信号
               int        sa_flags;                                 //使用哪一个信号处理对捕捉到的信号进行处理.可以是0表示使用sa_handler,也可以是SA_SIGINFO表示使用sa_sigaction
               void     (*sa_restorer)(void);                       //被废弃使用NULL
            };
*/
#include 
#include 
#include 
#include 
#include 
#include 
//过3秒后每隔2秒定时一次
void myalarm(int num){
    printf("捕捉到信号%d\n", num);
    printf("xxxx\n");
}
int main(){
    //注册信号捕捉
    struct sigaction act;
    act.sa_flags = 0;
    act.sa_handler = myalarm;
    sigemptyset(&act.sa_mask);
    sigaction(SIGALRM, &act, NULL);

    struct itimerval new_value;
    //设置间隔时间
    new_value.it_interval.tv_sec = 2;
    new_value.it_interval.tv_usec = 0;
    //设置延迟时间
    new_value.it_value.tv_sec = 3;
    new_value.it_value.tv_usec = 0;

    int ret = setitimer(ITIMER_REAL, &new_value, NULL);
    printf("定时器开始了\n");
    if(ret == -1){
        perror("setitimer");
        exit(-1);
    }
    while(1);
    return 0;
}

webserver笔记1-3_第78张图片

​ 信号捕捉过程

未决信号集只能处理一个未决信号,上一个未处理,下一个就会被丢弃。

SIGCHILD信号

webserver笔记1-3_第79张图片

#include 
#include 
#include 
#include 
#include 
#include 
void myFun(int num){
    printf("捕捉到的信号:%d\n", num);
    //回收子进程资源
    // wait(NULL);          //使用wait会丢弃一部分来不及处理的信号
    while(1){
        int ret = waitpid(-1, NULL, WNOHANG);
        if(ret > 0){
            printf("child die, pid:%d\n", ret);
        }else if(ret == 0){
            //还有子进程活着
            break;
        }else if(ret == -1){
            //没有子进程了
            break;
        }
    }
}
int main(){
    //提前设置阻塞信号集
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set, SIGCHLD);
    sigprocmask(SIG_BLOCK, &set, NULL);
    pid_t pid = fork();
    for(int i = 0; i < 20; i++){
        if(pid == 0){
            continue;
        }
        pid = fork();
    }
    if(pid > 0){
        //捕捉子进程死亡时发送的SIGCHLD信号
        struct sigaction act;
        act.sa_flags = 0;
        act.sa_handler = myFun;
        sigemptyset(&act.sa_mask);

        sigaction(SIGCHLD, &act, NULL);     //可能出现信号捕捉还没注册成功子进程就结束
        //注册完信号捕捉以后解除阻塞
        sigprocmask(SIG_UNBLOCK, &set, NULL);
        while(1){
            printf("parent process,pid:%d\n",getpid());
            sleep(2);
        }

    }else if(pid == 0){
        printf("child process,pid:%d\n",getpid());
    }
    return 0;
}

2.8 共享内存

进行进程间通信,比内存映射效率高

webserver笔记1-3_第80张图片

共享内存使用步骤

webserver笔记1-3_第81张图片

webserver笔记1-3_第82张图片

/*
#include 
#include 
    //创建一个新的共享内存段,获取一个既有的共享内存段的标识,新创建的内存段中的数据会被初始化为0
    int shmget(key_t key, size_t size, int shmflg);
        -key:通过这个找到或者创建一个共享内存,一般是16进制表示,非0
        -size:共享内存的大小,非0
        -shmflg:共享内存的属性
            -访问权限
            -附加属性:创建/判断共享内存是否存在
                -创建:IPC_CREAT
                -判断是否存在:IPC_EXCL, 需要和IPC_CREAT一起使用
                    IPC_CREAT | IPC_CREAT | 0664
        返回值:失败返回-1设置errno。成功返回>0,返回共享内存的引用ID,操作共享内存都是用这个值
    //删除共享内存,共享内存被删除才会消失,创建共享内存的进程被销毁,对共享内存没有影响
    int shmctl(int shmid, int cmd, struct shmid_ds *buf);
        -shmid:共享内存的标识
        -cmd:操作
            -IPC_STAT:获取共享内存的当前状态
            -IPC_SET:设置共享内存的状态
            -IPC_RMID:标记共享内存被销毁
        -buf:需要设置或者获取的共享内存的属性信息
            -IPC_STAT:  buf是传出参数,存储数据
            -IPC_SET:   buf中需要初始化的数据,设置到内核中
            -IPC_RMID:  没有用,设置NULL
            struct shmid_ds {
                struct ipc_perm shm_perm;    
                size_t          shm_segsz;  
                time_t          shm_atime;  
                time_t          shm_dtime;  
                time_t          shm_ctime;  
                pid_t           shm_cpid;   
                pid_t           shm_lpid;   
                shmatt_t        shm_nattch; 
                ...
            };
        返回值:失败返回-1设置errno,成功返回0
#include 
#include 
    //和当前的进程进行关联
    void *shmat(int shmid, const void *shmaddr, int shmflg);
        -shmid:共享内存的标识,shmget()的返回值
        -shmaddr:申请的共享内存的起始地址,指定NULL由内核指定
        -shmflg:对共享内存的操作
            -读权限:SHM_RDONLY必须有读权限
            -读写权限:0
        返回值:成功返回共享内存的首地址,失败返回(void*)-1
    //接触和共享内存的关联
    int shmdt(const void *shmaddr);
        -shmaddr:共享内存的首地址
        返回值:成功返回0,失败返回-1
#include 
#include 
    //根据指定的路径名和int值生成一个共享内存的key
    key_t ftok(const char *pathname, int proj_id);
        -pathname:指定一个存在的目录   /home/linux/
        -proj_id: 系统调用只会使用其中的1个字节(8位),范围 0-255 一般指定一个字符 'a'
*/

write.c

#include 
#include 
#include 
#include 
#include 
#include 
int main(){
    //创建一个共享内存
    int shmid = shmget(100, 4096, IPC_CREAT | 0664);
    //关联
    void *ptr = shmat(shmid, NULL, 0);
    //写数据
    char* str = "hello, world";
    memcpy(ptr, str, strlen(str) + 1);

    printf("按任意键结束。。。\n");
    getchar();

    //解除关联
    shmdt(ptr);
    // 删除共享内存
    shmctl(shmid, IPC_RMID, NULL);
    return 0;
}

read.c

#include 
#include 
#include 
#include 
#include 
#include 
int main(){
    //获取一个共享内存
    int shmid = shmget(100, 0, IPC_CREAT);
    //关联
    void *ptr = shmat(shmid, NULL, 0);
    //读数据
    printf("%s\n", (char*)ptr);

    printf("按任意键结束。。。\n");
    getchar();
    
    //解除关联
    shmdt(ptr);
    // 删除共享内存
    shmctl(shmid, IPC_RMID, NULL);
    return 0;
}

共享内存操作指令

webserver笔记1-3_第83张图片

问题1:操作系统如何知道一块共享内存被多少个进程关联?

  • 共享内存中维护了一个结构体 struct shmid_ds.shm_nattch记录了关联的进程的个数

问题2:可不可以对共享内存进行多次删除shmctl()

  • 可以,因为shmctl标记删除共享内存,不是直接删除.

问题3:什么时候真正删除?

  • 当和内存关联的进程数为0的时候,真正被删除

当共享内存的key为0时:如果共享内存被标记删除了,表示共享内存被标记删除了。如果一个进程和共享内存取消关联,就不能继续操作共享内存也不能再次关联

共享内存和内存映射的区别

  1. 共享内存可以直接创建,内存映射需要磁盘文件,匿名映射除外

  2. 共享内存效率更高

  3. 所有的进程操作的是同一块内存。内存映射,每个进程在自己的虚拟地址空间中有一个独立的内存。

  4. 进程突然退出,共享内存还存在。内存映射消失

  5. 运行进程的电脑死机,数据存储在共享内存中就没有了。

    内存映射区的数据由于磁盘文件的数据还在,所以内存映射区的数据还在

  6. 内存映射区:进程退出,内存映射区销毁

    共享内存:进程退出,共享内存还在,需要手动删除

2.9 守护进程

终端

webserver笔记1-3_第84张图片

进程组

webserver笔记1-3_第85张图片

会话

webserver笔记1-3_第86张图片

进程组、会话、控制终端的关系

webserver笔记1-3_第87张图片

操作

webserver笔记1-3_第88张图片

守护进程

webserver笔记1-3_第89张图片

守护进程创建步骤

webserver笔记1-3_第90张图片

//写一个守护进程,每隔2s获取下系统时间,将这个时间写入到磁盘文件中
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
void work(int num){
    //捕捉到信号获取系统时间,写入磁盘文件
    time_t tm = time(NULL);
    struct tm *loc = localtime(&tm);
    // char buf[1024];
    // sprintf(buf, "%d-%d-%d %d:%d:%d\n", loc->tm_year, loc->tm_mon, loc->tm_yday, loc->tm_hour, loc->tm_min, loc->tm_sec);
    // printf("%s\n", buf);
    char *str = asctime(loc);
    int fd = open("time.txt", O_RDWR | O_CREAT | O_APPEND, 0664);
    write(fd, str, strlen(str));
    close(fd);
}
int main(){
    //创建子进程
    pid_t pid = fork();
    //关闭父进程
    if(pid > 0){
        exit(0);
    }
    //提升为会话
    pid_t sid = setsid();
    //设置掩码
    umask(0222);
    //修改工作目录
    chdir("/linux/chapter2/lession17/");
    //关闭、重定向文件描述符
    // int fd = open("/dev/null", O_RDWR);
    // dup2(fd, STDIN_FILENO);
    // dup2(fd, STDOUT_FILENO);
    // dup2(fd, STDERR_FILENO);

    //业务逻辑
    //捕捉定时信号
    struct sigaction act;
    act.sa_flags = 0;
    act.sa_handler = work;
    sigemptyset(&act.sa_mask);

    sigaction(SIGALRM, &act, NULL);
    //设置定时器

    struct itimerval val;
    val.it_value.tv_sec = 2;
    val.it_value.tv_usec = 0;
    val.it_interval.tv_sec = 2;
    val.it_interval.tv_usec = 0;
    setitimer(ITIMER_REAL, &val, NULL);
    while(1){
        sleep(10);
    }
    return 0;
}

第3章 多线程开发

3.1 线程概述

概述

webserver笔记1-3_第91张图片

线程和进程区别

webserver笔记1-3_第92张图片

webserver笔记1-3_第93张图片

NPTL

webserver笔记1-3_第94张图片

3.2 线程创建

编译时gcc thread_create.c -o thread -pthread

/*
#include 
//创建一个子线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
    -*thread:传出参数,子线程的线程id被写到这个地址中
    -*attr:需要设置线程的属性,默认值用NULL
    -void *(*start_routine) (void *):函数指针,子线程需要处理的逻辑代码
    -*arg:给第三个参数使用,传参
    返回值:成功返回0,失败返回错误号(和errno不一样)。
    获取错误号的信息:#include    char *strerror(int errnum);
*/
#include 
#include 
#include 
#include 
void* callback(void *arg){
    printf("child thread...\n");
    printf(" %d\n", *(int *)arg);
    return NULL;
}
int main(){
    pthread_t tid;
    int num = 10;
    int ret = pthread_create(&tid, NULL, callback, (void *) &num);
    if(ret != 0){
        char* str = strerror(ret);
        printf("error: %s\n", str);
    }
    for(int i = 0; i < 5; i++){
        printf("%d\n", i);
        sleep(1);
    }
    return 0;
}

3.3 终止线程

/*
#include 
    //终止一个线程,在哪个线程调用就表示终止哪个线程
    void pthread_exit(void *retval);
        -retval:传出参数,作为返回值,可以在pthread_join()中获取
    //获取当前线程的线程id
    pthread_t pthread_self(void);
    //比较两个线程ID是否相同
    int pthread_equal(pthread_t t1, pthread_t t2);
    不同的操作系统的pthread_t类型的实现不一样,有的是无符号的长整型,有的是结构体
*/
#include 
#include 
#include 
#include 
void* callback(void *arg){
    printf("child thread tid=%ld...\n", pthread_self());
    printf(" %d\n", *(int *)arg);
    return NULL;    //相当于pthread_exit(NULL);
}
int main(){
    pthread_t tid;
    int num = 10;
    int ret = pthread_create(&tid, NULL, callback, (void *) &num);
    printf("tid=%ld\n",tid);
    if(ret != 0){
        char* str = strerror(ret);
        printf("error: %s\n", str);
    }
    printf("main thread id=%ld...\n", pthread_self());
    for(int i = 0; i < 5; i++){
        printf("%d\n", i);
    }
    //当主线程退出时,不会影响其他正常运行的线程
    pthread_exit(NULL);
    return 0;
}

3.4 连接已终止的线程

/*
#include 
    //连接已终止的线程,回收子线程的资源,这个函数是阻塞函数,调用一次只能回收一个子线程,一般用在主线程中
    int pthread_join(pthread_t thread, void **retval);
        -thread:要回收的子线程的id
        -retval:接收子线程退出时的返回值,二级指针
    返回值:成功返回0,失败返回错误号
*/

#include 
#include 
#include 
#include 
void* callback(void *arg){
    printf("child thread tid=%ld...\n", pthread_self());
    sleep(3);
    int value = 10;
    //不要返回局部变量,运行结束就会销毁
    pthread_exit((void*) &value);    //相当于pthread_exit(NULL);
}
int main(){
    pthread_t tid;
    int num = 10;
    int ret = pthread_create(&tid, NULL, callback, NULL);
    if(ret != 0){
        char* str = strerror(ret);
        printf("error: %s\n", str);
    }
    printf("main thread id=%ld...\n", pthread_self());
        //回收子线程的资源
    int *thread_retval;
    pthread_join(tid, (void*)&thread_retval);
    printf("exit data: %d\n", *thread_retval);
    for(int i = 0; i < 5; i++){
        printf("%d\n", i);
    }
    //当主线程退出时,不会影响其他正常运行的线程
    pthread_exit(NULL);
    return 0;
}

3.5 线程的分离

/*
#include 
    //分离一个线程,被分离的线程在终止的时候,会自动释放资源
        1. 不能多次分离
        2. 不能去连接一个已经分离的线程,会报错
    int pthread_detach(pthread_t thread);

*/
#include 
#include 
#include 
#include 
void * callback(void * arg){
    printf("child thread id:%ld\n", pthread_self());
    return NULL;
}
int main(){
    pthread_t tid;
    pthread_create(&tid, NULL, callback, NULL);
    //输出主线程和子线程的id
    printf("tid: %ld, main thread id: %ld\n", tid, pthread_self());
    //设置子线程分离
    pthread_detach(tid);
    pthread_exit(NULL);
    return 0;
}

3.6 线程取消

/*
#include 
    //取消线程(让进程终止)不是立即终止,而是运行到取消点时终止,取消点详见man pthreads--Cancellation points
    int pthread_cancel(pthread_t thread);

*/
#include 
#include 
#include 
#include 
#include 
void* callback(void * arg){
    printf("child id: %ld\n", pthread_self());
    for(int i = 0; i < 5; i++){
        printf("child %d\n", i);
        sleep(1);
    }
    return NULL;
}

int main(){
    pthread_t tid;
    int ret = pthread_create(&tid, NULL, callback, NULL);
    if(ret != 0){
        printf("create error:%s\n", strerror(ret));
        exit(0);
    }
    
    for(int i = 0; i < 5; i++){
        printf("main %d\n", i);
    }
    printf("tid: %ld, main thread id: %ld\n", tid, pthread_self());
    pthread_cancel(tid);
    return 0;
}

3.7 线程属性

/*
    //初始化属性变量
    int pthread_attr_init(pthread_attr_t *attr);
    //释放线程属性的资源
    int pthread_attr_destroy(pthread_attr_t *attr);
    //获取线程分离的属性
    int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
    //设置线程分离的状态属性
    int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
        -detachstate: PTHREAD_CREATE_DETACHED   设置detached
                      PTHREAD_CREATE_JOINABLE  默认值
*/
#include 
#include 
#include 
#include 
#include 
void * callback(void *arg){
    printf("child id: %ld\n", pthread_self());
    return NULL;
}
int main(){
    //创建一个线程属性变量
    pthread_attr_t attr;
    //初始化属性变量
    pthread_attr_init(&attr);
    //设置属性
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    
    pthread_t tid;
    pthread_create(&tid, &attr, callback, NULL);

    size_t stksize;
    pthread_attr_getstacksize(&attr, &stksize);
    printf("stack size is %ld\n", stksize);

    printf("tid: %ld, main thread id: %ld\n", tid, pthread_self());
    //释放线程属性资源
    pthread_attr_destroy(&attr);
    pthread_exit(NULL);
    return 0;
}

3.8 线程同步

线程同步

webserver笔记1-3_第95张图片

/*
    使用多线程实现卖票案例
    有3个窗口,一共100张票
    多个线程同时对共享的资源进行处理,会出现线程安全问题(线程同步问题)
*/
#include 
#include 
#include 
#include 
#include 
//设置全局变量,所有线程都共享这一资源
int tickets = 100;
void * sellticket(void *arg){
    while(tickets > 0){
        usleep(6000);
        printf("%ld 正在卖第%d 张票\n", pthread_self(), tickets);
        tickets--;
    }
    return NULL;
}

int main(){
    //创建3个子线程
    pthread_t tid1, tid2, tid3;
    pthread_create(&tid1, NULL, sellticket, NULL);
    pthread_create(&tid2, NULL, sellticket, NULL);
    pthread_create(&tid3, NULL, sellticket, NULL);
    //设置线程分离
    pthread_detach(tid1);
    pthread_detach(tid2);
    pthread_detach(tid3);
    pthread_exit(NULL);
    return 0;
}

互斥锁(互斥量)

webserver笔记1-3_第96张图片

webserver笔记1-3_第97张图片

webserver笔记1-3_第98张图片

/*
#include 
    //初始化互斥量
    int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
        -mutex:需要初始化的互斥量变量
        -attr:互斥量的属性,NULL
        -restrict: c语言的修饰符,被修饰的指针不能由另外的指针进行操作
    //释放互斥量
    int pthread_mutex_destroy(pthread_mutex_t *mutex);
    //加锁,阻塞的,如果有一个线程加锁了,那么其他的线程只能阻塞等待
    int pthread_mutex_lock(pthread_mutex_t *mutex);
    //尝试加锁,如果加锁失败,不会阻塞,直接返回
    int pthread_mutex_trylock(pthread_mutex_t *mutex);
    //解锁
    int pthread_mutex_unlock(pthread_mutex_t *mutex);
*/
#include 
#include 
#include 
#include 
#include 
//设置全局变量,所有线程都共享这一资源
int tickets = 100;

pthread_mutex_t mutex;
void * sellticket(void *arg){
    //卖票
    while(1){     //临界区
        //加锁
        pthread_mutex_lock(&mutex);
        if(tickets <= 0){
            //解锁
            pthread_mutex_unlock(&mutex);
            break;
        } 
        sleep(1);
        printf("%ld 正在卖第%d 张票\n", pthread_self(), tickets);
        tickets--;
        //解锁
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}

int main(){
    //初始化互斥量
    pthread_mutex_init(&mutex, NULL);
    //创建3个子线程
    pthread_t tid1, tid2, tid3;
    pthread_create(&tid1, NULL, sellticket, NULL);
    pthread_create(&tid2, NULL, sellticket, NULL);
    pthread_create(&tid3, NULL, sellticket, NULL);
    //设置线程分离
    pthread_detach(tid1);
    pthread_detach(tid2);
    pthread_detach(tid3);
    //退出主线程
    pthread_exit(NULL);
    //释放互斥量
    pthread_mutex_destroy(&mutex);
    return 0;
}

死锁

webserver笔记1-3_第99张图片

读写锁

webserver笔记1-3_第100张图片
webserver笔记1-3_第101张图片

/*
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
案例:8个线程操作同一个全局变量
3个线程不定时写这个全局变量,5个线程不定时读这个全局变量
*/
#include 
#include 
#include 
#include 
#include 
int num = 1;
pthread_rwlock_t rwlock;
void * readNum(void *arg){
    while(1){
        sleep(1);
        pthread_rwlock_rdlock(&rwlock);
        printf("read pid: %ld, num: %d\n", pthread_self(), num);
        pthread_rwlock_unlock(&rwlock);
    }
    return NULL;
}
void * writeNum(void *arg){
    while(1){
        sleep(2);
        pthread_rwlock_wrlock(&rwlock);
        num++;
        printf("%ld thread write, num: %d\n", pthread_self(), num);
        pthread_rwlock_unlock(&rwlock);
    }
    return NULL;
}
int main(){
    //初始化读写锁
    pthread_rwlock_init(&rwlock, NULL);
    //创建3个子线程
    pthread_t rds[5], wrs[3];
    for(int i = 0; i < 5; i++){
        pthread_create(&rds[i], NULL, readNum, NULL);
    }
    for(int i = 0; i < 3; i++){
        pthread_create(&wrs[i], NULL, writeNum, NULL);
    }
    //设置线程分离
    for(int i = 0; i < 5; i++){
        pthread_detach(rds[i]);
    }
    for(int i = 0; i < 3; i++){
        pthread_detach(wrs[i]);
    }
    //退出主线程
    pthread_exit(NULL);
    //释放读写锁
    pthread_rwlock_destroy(&rwlock);
    return 0;
}

生产者和消费者模型

/*
    生产者消费者模型

*/
#include 
#include 
#include 
#include 
#include 
pthread_mutex_t mutex;
struct Node
{
    int num;
    struct Node *next;
};
struct Node* head = NULL;

void * producer(void * arg){
    //不断创建新的结点添加到链表中
    while(1){
        pthread_mutex_lock(&mutex);
        struct Node *newNode = (struct Node *)malloc(sizeof(struct Node));
        newNode->next = head;
        head = newNode;
        newNode->num = rand() % 100;
        printf("add node, num: %d, tid:%ld\n", newNode->num, pthread_self());
        pthread_mutex_unlock(&mutex);
        usleep(1000);
    }
    return NULL;
}
void * customer(void * arg){
    while(1){
        pthread_mutex_lock(&mutex);
        struct Node *tmp = head;
        if(head != NULL){
            head = head->next;
            printf("del node, num: %d, tid: %ld\n", tmp->num, pthread_self());
            free(tmp);
            pthread_mutex_unlock(&mutex);
            usleep(1000);
        }else{
            pthread_mutex_unlock(&mutex);
        }
        
    }
    
    return NULL;
}
int main(){
    pthread_mutex_init(&mutex, NULL);
    pthread_t ptids[5], ctids[5];
    for(int i = 0; i < 5; i++){
        pthread_create(&ptids[i], NULL, producer, NULL);
        pthread_create(&ctids[i], NULL, customer, NULL);
    }
    for(int i = 0; i < 5; i++){
        pthread_detach(ptids[i]);
        pthread_detach(ctids[i]);
    }
    while(1){
        sleep(1);
    }
    pthread_mutex_destroy(&mutex);
    pthread_exit(NULL);
    return 0;
}

条件变量

webserver笔记1-3_第102张图片

/*
//等待多长时间,调用了该函数,线程会等待,直到时间结束
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
//阻塞函数,调用了该函数,线程会等待
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
//释放条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
//初始化条件变量
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
//唤醒一个或多个线程
int pthread_cond_signal(pthread_cond_t *cond);
//唤醒所有线程
int pthread_cond_broadcast(pthread_cond_t *cond);
*/
#include 
#include 
#include 
#include 
#include 
pthread_mutex_t mutex;
pthread_cond_t cond;
struct Node
{
    int num;
    struct Node *next;
};
struct Node* head = NULL;

void * producer(void * arg){
    //不断创建新的结点添加到链表中
    
    while(1){
        pthread_mutex_lock(&mutex);
        struct Node *newNode = (struct Node *)malloc(sizeof(struct Node));
        newNode->next = head;
        head = newNode;
        newNode->num = rand() % 100;
        printf("add node, num: %d, tid:%ld\n", newNode->num, pthread_self());
        //只要生产一个就通知消费者
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&mutex);
        usleep(1000);
    }
    return NULL;
}
void * customer(void * arg){
    while(1){
        pthread_mutex_lock(&mutex);
        struct Node *tmp = head;
        if(head != NULL){
            //有数据
            head = head->next;
            printf("del node, num: %d, tid: %ld\n", tmp->num, pthread_self());
            free(tmp);
            pthread_mutex_unlock(&mutex);
            usleep(1000);
        }else{
            //没有数据
            pthread_cond_wait(&cond, &mutex);   //wait阻塞的时候会先解锁mutex。不阻塞的时候,继续向下运行重新加锁
            pthread_mutex_destroy(&mutex);
        }
        
    }
    
    return NULL;
}

int main(){
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond, NULL);
    pthread_t ptids[5], ctids[5];
    for(int i = 0; i < 5; i++){
        pthread_create(&ptids[i], NULL, producer, NULL);
        pthread_create(&ctids[i], NULL, customer, NULL);
    }
    for(int i = 0; i < 5; i++){
        pthread_detach(ptids[i]);
        pthread_detach(ctids[i]);
    }
    while(1){
        sleep(1);
    }
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);
    pthread_exit(NULL);
    return 0;
}

3.9 信号量

webserver笔记1-3_第103张图片

/*
#include 
    //初始化信号量
    int sem_init(sem_t *sem, int pshared, unsigned int value);
        -sem: 信号量变量的地址
        -pshared:0:用在线程间,非0:用在进程间
        -value: 记录信号量中的值
    //释放资源
    int sem_destroy(sem_t *sem);
    //对信号量加锁调用一次的值减一,如果为0,则阻塞
    int sem_wait(sem_t *sem);
    //
    int sem_trywait(sem_t *sem);
    //
    int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
    //解锁一个信号量,调用一次对信号量的值加一
    int sem_post(sem_t *sem);
    //
    int sem_getvalue(sem_t *sem, int *sval);

---------------------------------------------

    sem_t psem;     理解为容器容量
    sem_t csem;     理解为容器内容个数
    init(psem, 0, 8);
    init(csem, 0, 0);
    producer(){
        sem_wait(&psem);    //生产了一个,psem - 1


        sem_post(&csem);    //通知消费者,csem + 1
    }
    customer(){
        sem_wait(&csem);    //可以消费的个数

        sem_post(&psem);    //通知生产者, psem + 1
    }
*/
#include 
#include 
#include 
#include 
#include 
#include 
pthread_mutex_t mutex;
sem_t psem, csem;

struct Node
{
    int num;
    struct Node *next;
};
struct Node* head = NULL;

void * producer(void * arg){
    //不断创建新的结点添加到链表中
    while(1){
        sem_wait(&psem);
        pthread_mutex_lock(&mutex);
        struct Node *newNode = (struct Node *)malloc(sizeof(struct Node));
        newNode->next = head;
        head = newNode;
        newNode->num = rand() % 100;
        printf("add node, num: %d, tid:%ld\n", newNode->num, pthread_self());
        //只要生产一个就通知消费者
        pthread_mutex_unlock(&mutex);
        sem_post(&csem);
        usleep(1000);
    }
    return NULL;
}
void * customer(void * arg){
    while(1){
        sem_wait(&csem);
        pthread_mutex_lock(&mutex);
        struct Node *tmp = head;
        //有数据
        head = head->next;
        printf("del node, num: %d, tid: %ld\n", tmp->num, pthread_self());
        free(tmp);
        pthread_mutex_unlock(&mutex);
        sem_post(&psem);
        usleep(1000);
    }
    
    return NULL;
}

int main(){
    pthread_mutex_init(&mutex, NULL);
    sem_init(&psem, 0, 8);
    sem_init(&csem, 0, 0);
    pthread_t ptids[5], ctids[5];
    for(int i = 0; i < 5; i++){
        pthread_create(&ptids[i], NULL, producer, NULL);
        pthread_create(&ctids[i], NULL, customer, NULL);
    }
    for(int i = 0; i < 5; i++){
        pthread_detach(ptids[i]);
        pthread_detach(ctids[i]);
    }
    while(1){
        sleep(1);
    }
    pthread_mutex_destroy(&mutex);
    sem_destroy(&psem);
    sem_destroy(&csem);
    pthread_exit(NULL);
    return 0;
}

前三章重点内容

  • 第一章:

    • 静态库动态库
    • 虚拟地址空间
    • 文件描述符
    • 系统函数
  • 第二章:

    • 进程状态转换
    • 进程创建
    • exec函数族
    • 孤儿进程僵尸进程
    • 进程间通信
    • 信号
    • 守护进程
  • 第三章:

    • 线程库API使用

    • 线程进程区别:线程之间共享虚拟地址空间,进程是复制虚拟地址空间

    • 线程同步

    • 生产者消费者模型

你可能感兴趣的:(学习,后端)