Linux高并发服务器开发(一)Linux系统编程入门

GCC

        GNU(GNU C Compiler GNU C语言编译器 --> GNU Compiler Collection) 

        安装命令 sudo apt install gcc g++

        查看版本 gcc/g++ -v/--version

ctrl+l :清空目录

编程语言的发展

        计算机 --> 机器语言 -->  汇编语言 --> 高级语言

gcc test.c :    

        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 -- 链接成可执行文件 

gcc常用参数

gcc 和 g++ 的区别

        gcc和g++都是GNU的一个编译器

        后缀为.c的gcc会把它当作C程序,而g++会把它当作C++程序;后缀为.cpp的,两者都会认为是C++程序,C++的语法规则更加严谨一些

        编译阶段,g++会调用gcc,对于C++代码,两者是等价的,但是因为gcc命令不能自动和C++程序使用的库连接,所以通常用g++来完成链接。为了统一起见,干脆编译/链接统统用g++了

        编译可以用gcc/g++,而链接可以用g++或者gcc -lstdc++

        gcc命令不能自动和C++程序使用的库联接,所以通常使用g++来完成联接。但是在编译阶段,g++会自动调用gcc,二者等价

静态库

什么是库?

        库文件是计算机上的一类文件,可以简单的把库文件看成一种代码仓库,提供给使用者一些可以直接拿来用的变量、函数或类

        库是一种特殊的程序,编写库的程序和编写一般的程序区别不大,只是库不能单独运行

        库文件有两种:静态库和动态库(共享库)。静态库在程序的链接阶段被复制到了程序中;动态库在链接阶段没有被复制到程序中,而是程序在运行时由系统动态加载到内存中供程序调用

        库的好处是:1、代码保密;2、方便部署和分发

静态库的命名规则

        Linux:libxxx.a

                (lib—>固定前缀;xxx—>库的名字,自定义;.a—>固定后缀)

        Windows:libxxx.lib

静态库的制作

        gcc 获得.o文件

                gcc -c xxx.c xxx.c

        将.o文件打包,使用ar工具(archive)

                ar rcs libxxx.a xxx.o xxx.o

                        r — 将文件插入备存文件中

                        c — 建立备存文件

                        s — 索引

静态库的使用

其中,app是生成的可执行文件(gcc main.c -o app -I ./include/ -l calc -L ./lib)(第一个是大写的i,第二个是小写的L)(填入要指定的库时,只需要填写库的名称即可(calc))----------------------------------------在该文件下,include是用来放置头文件的,lib是放置库文件(静态库的制作),mian函数是测试文件,src是源代码文件(source)-----------------------------------静态库的制作:1. gcc -c add.c sub.c mult.c div.c -I ../include/ (该目录下没有头文件,得去上级目录下的include文件夹中寻找头文件)。2. ar rcs libcalc.a add.o div.o mult.o div.o(通过ar指令对.o文件进行打包处理)3.mv libcalc.a ../lib/ (将libcalc.a 移动到lib文件夹中)

动态库

动态库的命名规则

        Linux:libxxx.so

                在Linux下是一个可执行文件

        Windows:libxxx.dll

动态库的制作

        gcc 得到 .o文件,得到和位置无关的代码fpic

                gcc -c -fpic/-fpIC a.c b.c

                -fpic 用于编译阶段,产生的代码没有绝对地址,全部用相对地址,这正好满足了共享库的要求,共享库被加载时地址不是固定的。如果不加-fpic ,那么生成的代码就会与位置有关,当进程使用该.so文件时都需要重定位,且会产生成该文件的副本,每个副本都不同,不同点取决于该文件代码段与数据段所映射内存的位置。

        gcc 得到动态库

                gcc -shared a.o b.o -o libcalc.so

动态库加载失败的原因

        在运行一个使用动态库文件时,按照动态库的制作会出现一些问题

动态库的工作原理

原因是静态库和动态库的工作原理不同:静态库在GCC进行链接时,会把静态库中代码打包到可执行程序中;而动态库在GCC进行链接时,动态库的代码不会被打包到可执行程序中。程序启动后,动态库会被动态加载到内存中,通过ldd (list dynamic dependecies) 命令检查动态库的依赖关系

如何定位共享库文件? --   当系统加载可执行代码时,能够知道其所依赖的库的名字,但是还需知道绝对路径。此时就需要系统的动态载入器来获取该绝对路径。对于elf格式的可执行程序,是由ld-linux.so来完成的,它先后搜索elf文件的 DT_RPATH段 --> 环境变量LD_LIBRARY_PATH --> /etc/ld.so.cache文件列表 --> /lib/, /usr/lib目录找到库文件后将其载入内存

一、对于修改LD_LIBRARY_PATH的三种方法

解决方案一:在终端中用export命令在LD_LIBRARY_PATH中添加库文件的绝对路径(缺点:在终端关闭后该环境变量会消失)

解决方案二:(用户级别永久配置环境变量)

        vim .bashrc (在home目录下,有一个.bashrc文件,直接在该文件最末行(shift+g)插入环境变量),再 . .bashrc(第一个.相当于source,所以也可以用source .bashrc) 使文件生效

解决方案三:(系统级别永久配置环境变量)

        系统级别则需要sudo权限,在etc目录下的profile文件内添加环境变量,source一下使文件生效(如果source不能使用则使用.命令是一样的) 

二、对/etc/ld.so.cache文件列表进行配置的方法 

三、在 /lib/, /usr/lib目录下加入动态库文件

        不推荐使用,因为本身就有很多系统自带的库文件,可能会出现重名替换问题(防止误操作)

静态库和动态库的对比​​​​​​​​​​​​​​

静态库动态库
优点

打包到应用程序中加载速度快;

发布程序无需提供静态库,移植方便

可以实现进程间的资源共享(共享库)

更新、部署、发布简单

可以控制何时加载动态库

缺点

消耗系统资源,浪费内存;

更新、部署、发布麻烦

加载速度比静态库慢

发布程序时需要提供依赖的动态库

Makefile

什么是Makefile?

        一个工程中的源文件不计其数,按类型、功能、模块分别放在若干个目录中,Makefile文件定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为Makefile文件就像一个Shell脚本一样,也可以执行操作系统的命令。

        Makefile带来的好处就是“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高的软件开发的效率。make是一个命令工具,是一个解释Makefile文件中指令的命令工具,一般来说,大多数的IDE都有这个命令。

Makefile文件命名和规则

        文件命名:makefile 或 Makefile

        一个Makefile文件中可以有一个或者多个规则,任何其他规则都是为第一个规则所服务的 

一个规则

 1、打开Makefile进行编写(vim Makefile):

2、 然后执行make指令,会自动帮我们寻找当前目录下的Makefile文件

工作原理

        命令在执行之前,需要先检查规则中的依赖是否存在:

                存在,则执行命令

                不存在,则向下检查其他的规则,检查有没有一个规则是用来生成这个依赖的,如果找到了,则执行该规则中的命令

        检查更新,在执行规则的命令时,会比较目标和依赖文件的时间:

                如果依赖的时间比目标的时间早,目标不需要更新,对应规则中的命令不需要被执行

变量

模式匹配 

函数

wildcard:

 patsubst:

 

GDB调试

什么是GDB?

        由GNU软件系统社区提供的调试工具,同GCC配套组成了一套完整的开发环境,GDB是Linux和许多类Unix系统中的标准开发

        一般来说,GDB主要帮助完成以下四方面的功能:1、启动程序,可以按照自定义的要求随心所欲的运行程序;2、可让被调试程序在所指定的调置的断点处停住(断点可以是条件表达式);3、当程序被停住时,可以检查此时程序中所发生的事;4、可以改变程序,将一个BUG产生的影响修正从而测试其他BUG

准备工作

        通常情况下,在为调试而编译时,我们会()关掉编译器的优化选项(`-O`),并打开调试选项(`-g`);另外,`-Wall`在尽量不影响程序行为的情况下选项打开所有warning,也可以发现许多问题,避免一些不必要的BUG

        gcc -g -Wall program.c -o program

        `-g` --> 在可执行文件中加入源代码信息。比如可执行文件中第几条机器指令对应源代码的第几行,但并不是把整个源文件嵌入到可执行文件中,所以在调试时必须保证gdb能找到源文件

GDB命令

        启动 -- gdb 可执行程序                退出 -- quit/q

        给程序设置参数 -- set args 10 20        获得设置参数 -- show args

        GDB 使用帮助 -- help

        查看当前文件代码 -- list/l(从默认位置显示);list/l 行号(从指定的行显示--该行号在中间位置);list/l 函数名(从指定的函数显示)

        查看非当前文件代码 -- list/l 文件名:行号;list/l 文件名:函数名

        设置显示的行数 -- show list/listsize;show list/listsize 行数

        设置断点 -- b/break 行号;b/break 函数名;b/break 函数名;b/break 文件名:行号;b/break 文件名:函数

        查看断点:i/info b/break

        删除断点:d/del/delete 断点编号

        设置断点无效:dis/disable 断点编号

        设置断点生效:ena/enable 断点编号

        设置条件断点(一般用在循环的位置):b/break 10 if i==5

常用GDB调试命令

        运行GDB程序:start(程序停在第一行);run(遇到断点才停)

        继续运行,到下一个断点停:c/continue

         向下执行一行一行代码(不会进入函数体):n/next

        变量操作:p/print 变量名(打印变量值);ptype 变量名(打印变量类型)

        向下单步调试(遇到函数进入函数体):s/stepfinish(跳出函数体:不能有断点)

        自动变量操作:display num(自动打印指定变量的值);i/info display;

underplay 编号

         其他操作:set var 变量名=变量值;until(跳出循环:不能有断点&&在该循环的最后一句)       

文件IO

在C标准库中的IO函数:可跨平台:

 虚拟地址空间

 内存空间大小由电脑的CPU决定。以32位内存空间为例,虚拟内存空间为2的32次方(约4G) (64位为2的48次方);

虚拟地址空间氛围0~3G用户区和3~4G的内核区。虚拟地址空间的数据最终会被cpu中的逻辑管理单元MMU映射到真实的物理内存上;

内核区的数据普通用户没有权限操作;若要操作内核中的数据,则需要进行系统调用(调用Linux系统的API)

文件描述符

用来定位磁盘上的文件位置,位于内核区,由内核进行管理(具体是由PCB进程控制块管理,PCB中有一个文件描述符表,是一个数组,用于存储文件描述符)

文件描述符表(该数组默认大小为1024)中,前三个是默认被占用的(标准输入、标准输出、标准错误)指向的是当前终端。若文件描述符被占用,则将去文件描述符表中找一个最小的文件描述符去使用(由内核维护)

Linux系统IO函数

        在Linux中输入“man 2 open”能看到Linux的open手册,其中要声明三个头文件:

#include <sys/types.h>
#include <sys/stat.h>
//定义的宏(flags)放在了以上两个头文件中
#include <fcntl.h> //open函数声明在该头文件中

int open(const char *pathname, int flags); //打开一个已经存在的文件

        参数:

                pathname -- 要打开的文件路径

                flags --  对文件的操作权限设置还有其他的设置(只读、只写、可读可写;三者互斥)

        返回值:返回一个新的文件描述符(若成功,则返回文件描述符的号码;失败返回-1)

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

perror:打印errno对应的错误描述;

参数s:用户描述,比如hello,最终输出的内容是 hello:xxx(xxx指的是实际的错误描述

int open(const char *pathname, int flags, mode_t mode); //创建一个新文件

        与之前的int open(const char *pathname, int flags);不是函数重载(C语言中没有函数重载),是通过可变参数实现了一个同名的效果

        参数:

                pathname -- 要创建的文件路径

                flags --  对文件的操作权限设置还有其他的设置,用“|(按位或)”增加可选项

                        必选项:只读、只写、可读可写;三者互斥

                        可选项:O_APPEN(向文件内追加)O_ASYNC(同步)...O_CREAT(文件不存在,则创建新文件)

                mode -- 八进制数,表示用户对创建出的新的操作权限,比如:0777,0为八进制的数。

终权限 = (mode & ~umask),如umask=0002(取反结果为0775),mode=0777,则进行运算:0775(会少了某些权限)umask的作用是抹去某些权限,让文件权限更合理一些

         

int close(int fd);

关闭一个文件描述符使得它不能再指向任何一个文件;且该文件描述符还能被重用

ssize_t read(int fd, void *buf, size_t count); //从文件中读取数据到内存中

        参数:

                fd --> 文件描述符,open得到的,通过这个文件描述符来操作某个文件

                buf --> 需要读取数据存放的地方,数组的地址,(传出参数)

                count --> 指定的数组的大小

        返回值:若成功,则会返回实际读取到字节的数量;若返回0(也为成功),则表示已经读到了文件末尾(读完了);若出现错误,则返回-1(null),并把errno设置为合适的值

ssize_t write(int fd, const void *buf, size_t count); //把内存中的数据写到文件中

        参数:

                buf --> 要往磁盘写入的数据,一般是数组

                count --> 要写的数据的实际的大小

        返回值:若成功,则返回实际的写入的字节数;若为0,则表示没有写入任何内容;若为-1,则表示写入失败 

off_t lseek(int fd, off_t offset, int where); //对标标准C库中的fseek

        fd --> 通过open得到的文件描述符,通过fd去操作某个文件

        offset --> 偏移量

        whence --> 指定一些标记

whence的一些可选项

SEEK_SET​​​​​​​ 设置文件指针的偏移量

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

SEEK_END 设置偏移量:文件的大小+第二个参数offset的值

        返回值:返回最终文件指针的位置

lseek函数的作用:

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

2、获取当前文件指针的位置: lseek(fd, 0, SEEK_CUR);

3、获取文件长度: lseek(fd, 0, SEEK_END);

4、拓展文件的长度: lseek(fd, 100, SEEK_END);  (如,当前文件10b,要拓展成110b,增加了100个字节:【注意】在拓展文件长度之后再写入一个空数据(拓展文件后需要写入一次数据才会生效),查看文件时会发现该文件从10b变成了111b( write(fd, " ", 1); )),拓展的字符(100b)都是空字符

int stat(const char *pathname, struct stat *statbuf); //获取文件的信息

        作用:获取文件的相关信息

                在Linux终端中,可以输入stat指令,后面跟上想要查看的文件名查看

        参数:

                pathname --> 操作的文件的路径

                statbuf --> 结构体变量,传出参数,用于保存获取到的文件信息

        返回值:成功则返回0,失败则返回-1,设置errno

stat结构体 内容

                其中,mode_t st_mode;一共16位,用标志位变量记录文件的类型和存储权限如下: 

st_mode变量

若想要判断的权限,需要将所求权限与原先的二进制码做与的操作;若想判断文件类型,则需要将原先的数值和掩码进行与操作【st_mode & S_IFMT】 

int lstat(const char *pathname, struct stat *statbuf); //获取软链接文件的信息

        若想获取软链接的信息,则使用lstat函数:当一个文件指向另一个文件时(软链接),用stat得到的是另一个文件的信息,用lstat才能得到软链接文件本身的信息

文件属性操作函数

int access (const char *pathname, int mode); //判断文件的权限或者判断文件是否存在

        参数:

                pathname --> 判断的文件路径

                mode --> 要判断的权限(F_OK:测试文件是否存在;R_OK/ W_OK/ X_OK:测试文件是否有读/写/可执行权限) 

        返回值:成功返回0;失败返回-1

int chmod (const char *filename, int mode); //修改文件权限​​​​​​​

        参数:

                pathname --> 需要修改的文件的路径

                mode--> 需要修改的权限值,八进制的数

        返回值: 成功返回0;失败返回-1

int chown (const char *path, uid_t owner, gid_t group); //修改文件所有者或所在组

在 etc/passwd里面可以看到 --> 用户名:所有者id:所在组id;在 /etc/group 查看所有组

int truncate (const char *path, off_t length); //缩减或扩展某个文件尺寸至指定大小

         参数:

                path --> 需要修改的文件的路径

                length --> 需要最终文件变成的大小

        返回值: 成功返回0;失败返回-1

目录操作函数

int mkdir (const char *pathname, mode_t mode); //创建目录

         参数:

                pathname --> 创建的目录路径

                mode --> 权限,八进制的数

        返回值: 成功返回0;失败返回-1        

int rmdir (const char *pathname); //删除一个空文件

in rename (const char *oldpath, const char *newpath); //对目录进行重命名

int chdir (const char *path); //修改进程的工作路径/目录 

        参数:

                path --> 需要修改的工作路径

char *getcwd (char *buf, size_t size); //获取当前进程工作的路径/目录

        参数:

                buf --> 存储的路径,指向的是一个数组

                size --> 数组的大小

        返回值: 返回的指向的一块内存,这个数据就是第一个数组

目录遍历函数

以下三个函数可以实现 读取某个目录下普通文件的个数

DIR *opendir (const char * name); //打开一个目录

        参数: 

                name --> 需要打开的目录

        返回值:

                DIR * 类型 --> 理解为目录流,其实是一个结构体

                错误返回NULL

struct dirent *readdir (DIR *dirp); //读取目录里的内容

        调用一次dirent,就会指向下一个实体(会向后移)

        参数:

                dirp --> 是opendir返回的结果

        返回值:

                若读取到末尾或者失败了,就返回NULL

                struct dirent --> 结构体,代表读取到的文件信息

int closedir (DIR dirp); //关闭一个目录​​​​​​​

文件描述符操作相关函数

int dup (int oldfd); //复制一个新的文件描述符

        参数及返回值:

                oldfd --> 旧的文件描述符,它和新的文件描述符都指向同一个文件(如,fd=3, int fd1 = dup(fd),其中fd指向的是a.txt,fd1也指向a.txt),从空闲的文件描述符表中找一个最小 ,作为新的拷贝的文件描述符

int dup2 (int oldfd, int newfd); //重定向文件描述符

        将oldfd拷贝给newfd。如,有一个oldfd指向a.txt,newfd指向b.txt。调用函数成功后,newfd 和 b.txt 做close,newfd 指向了 a.txt(即newfd指向了和oldfd相同的文件)

        oldfd必须是一个有效的文件描述符;

        若oldfd和newfd值相同,相当于什么都没做;

int fcntl (int fd, int cmd, ... /* arg */ ); 

        参数:

                fd --> 需要操作的文件描述符

                cmd -->  command传递的一个命令(一些定义的宏)

                        F_DUPFD:复制文件描述符,复制的是第一个参数fd,得到一个新的文件描述度(通过返回值)

                        F_GETFL:获取指定的文件描述符文件状态flag(文件状态flag 的 我们通过open函数传递的flag是同一个东西

                        F_SETFL:设置文件描述符文件状态flag(必选项O_RDONLY,O_WRONLY,O_RDWR不可以被修改;可选项有O_APPEND,O_NUNBLOCK等)

--> O_APPEND表示追加数据;O_NUNBLOCK表示设置成非阻塞

阻塞和非阻塞:描述的是函数调用的行为。 阻塞行为是指在调用一个函数的过程中、该函数的返回值还没有返回之前,其他进程/线程被挂起;非阻塞行为是指调用一个函数后立马就能返回(不管是否得到想要的结果),不会影响其他进程/线程的情况

                ... --> 可变参数,可以没有,也可以有多个参数

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值