Linux笔记

一、Linux开发

  • 换源

    vi /etc/apt/sources.list
    
    :1,$d	//第一行到末行,d删除
    
    # 默认注释了源码镜像以提高 apt update 速度,如有需要可自行取消注释
    deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy main restricted universe multiverse
    # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy main restricted universe multiverse
    deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-updates main restricted universe multiverse
    # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-updates main restricted universe multiverse
    deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-backports main restricted universe multiverse
    # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-backports main restricted universe multiverse
    
    deb http://security.ubuntu.com/ubuntu/ jammy-security main restricted universe multiverse
    # deb-src http://security.ubuntu.com/ubuntu/ jammy-security main restricted universe multiverse
    
    # 预发布软件源,不建议启用
    # deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-proposed main restricted universe multiverse
    # # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-proposed main restricted universe multiverse
    
    apt update
    apt upgrade
    
(一)杂记
  • 安装编译器
//安装编译器
apt -y install gcc
g++
//安装帮助手册
apt -y install man-db
//解除最小安装
unminimize
  • man级别
1.gcc/g++
  • gcc/g++ 选项 源代码文件1 源代码文件2 n

  • 常用选项

    • -o,指定输出的文件名,如果不给,则生成可执行文件a.out
    • -g,调试用
    • -On,在编译、链接过程中进行优化处理,生成的可执行程序效率更高
    • -c,只编译,不链接成可执行文件,通常用于把源文件编译成静态库或动态库
  • 优化选项,编译时间将更长、目标程序不可调试、有效果但是不可能显著提升程序的性能

    • -O0:不做任何优化,这是默认的编译选项
    • -O或O1:对程序做部分编译优化,对于大函数,优化编译占用稍微多的时间和相当大的内存。使用本项优化,编译器会尝试减小生成代码的尺寸,以及缩短执行时间,但并不执行需要占用大量编译时间的优化
    • -O2:推荐使用,比O1更高级的选项,优化增加了编译时间的基础上,提高了生成代码的执行效率
    • -O3:比o2更进一步的优化,很危险
2.静态库和动态库
(1)静态库
  • 制作静态库
g++ -c -o lib库名.a 源代码文件清单
  • 使用静态库
g++ 选项 源代码文件名清单 -l 库名 -L 库文件所在的目录名
  • 静态库的概念

    • 程序在编译时会把库文件的二进制代码链接到目标程序中,这种方式称为静态链接
    • 如果多个程序中用到了同一静态库中的函数,就会存在多份拷贝
  • 静态库的特点

    • 静态库的链接是在编译期完成的,执行的时候代码加载速度快
    • 目标程序的可执行文件比较大,浪费空间
    • 程序的更新和发布不方便,如果某一个静态库更新了,所有使用它的程序都需要重新编译
(2)动态库
  • 制作动态库
g++ -fPIC -shared -o lib库名.so 源代码文件清单
  • 使用动态库,运行可执行程序的时候,需要提前设置LD_LIBRARY_PATH环境变量,添加动态库目录
g++ 选项 源代码文件名清单 -l 库名 -L 库文件所在的目录名
  • 动态库的概念
    • 程序在编译时不会把库文件的二进制代码链接到目标程序中,而是在运行的时候才被载入
    • 如果多个程序中用到了同一动态库的函数,那么在内存中只有一份,避免了空间浪费问题
  • 动态库的特点
    • 程序运行在运行的过程中,需要用到动态库的时候才把动态库的二进制代码载入内存
    • 可以实现进程之间的代码共享,因此静态库也称为共享库
    • 程序升级比较简单,不需要重新编译程序,只需要更新动态库就行了
  • 如果动态库和静态库同时存在,编译器将优先使用动态库
3.makefile
  • 增量编译,无变化的不需要重新编译,可以用变量替换库文件等路径
//指定编译的目标文件是libpublic.a和libpublic.so、lib...
all:libpublic.a libpublic.so \
    lib....	
//编译libpublic.a需要依赖public.h和public.cpp
//如果被依赖文件内容发生了变化,将重新编译libpublic.a
libpublic.a:public.h public.cpp
    g++ -c -o libpublic.a public.cpp
    //-I库文件路径
    //可以执行其他命令比如cp demo01 /tmp
libpublic.so:public.h public.cpp
    g++ -fPIC -shared -o libpublic.so public.cpp

//clean用于清理编译目标文件,仅在make clean才会执行
clean:
	rm -f libpublic.a libpublic.so
4.main函数的参数
  • 有三个参数
int main(int argc,char* argv[],char* envp[]){
	return 0;
}
//argc,存放了程序参数的个数,包括程序本身
//argc,字符串的数组,存放了每个参数的值,包括程序本身
//envp,字符串的数组,存放了环境变量,数组的最后一个元素是空;env命令
  • 设置环境变量
int setenv(const char* name,const char* value,int overwrite);
//name,环境变量名
//value,环境变量的值
//overwrite,0-如果环境不存在,增加新的环境变量,如果环境变量已存在,不替换其值;非0-如果环境变量不存在,增加新的环境变量,如果环境变量已存在,替换其值
//返回值:0-成功,-1-失败
//调用此函数设置的环境变量只对本进程有效,仅本次有效,不会影响shell的环境变量
  • 获取环境变量的值
char* getenv(const char* name);
5.gdb的常用命令
  • 安装gdb
apt -y install gdb
  • 编译时需要加-g选项(编译后的二进制文件会包含源代码),并且不能使用-O的优化选项(优化后执行的顺序可能不一样)
gdb 目标程序
  • 命令:
    • set args,设置程序运行的参数,set args 参数1 参数2 …
    • break, b, 设置断点,b 20表示在第20行设置断点,可以设置多个断点
    • run, r, 开始运行程序,程序运行到断点的位置会停下来,如果没有遇到断点,程序一直运行下去
    • next, n, 执行当前行语句,如果该句为函数调用,不会进入函数内部,VS中F10
    • step, s, 执行当前行语句,如果该句为函数调用,则进入函数内部,此外,如果函数是库函数或第三方提供的函数,也进不去,因为没有源代码,VS中F11
    • print, p, 显示变量或者表达式的值,如果p后面是表达式,会执行这个表达式(赋值操作可改变变量值)
    • continue, c, 继续运行程序,直到遇到下一个断点,VS中F5
    • set var, 设置变量的值
    • quit, q, 退出gdb
    • bt, 查看函数调用栈
  • gdb调试core文件,如内存泄漏
    • linux缺省不会生成core文件,需要修改系统参数
      • ulimit -a查看当前用户的资源限制参数
      • ulimit -c unlimitedcore file size改为unlimited
      • 运行程序,产生core文件
      • 运行gdb 程序名 core文件名
      • 在gdb中,用bt查看函数调用栈
  • gdb调试正在运行中的程序
    • 获取进程编号,ps -ef |grep demo
    • gdb 程序名 -p 进程编号
    • 进入调试程序暂停,退出调试程序继续
6.linux的时间操作
  • time_t别名

    • time_t用于表示时间类型,它是一个long类型的别名,在<time.h>文件中定义,表示从1970年1月1日0时0分0秒到现在的秒数
    • typedef long time_t
  • time()库函数

    • time_t time(time_t *tloc);
    time_t now = time(0);		//将空地址传递给time()函数,并将time()返回值赋给变量now
    time_t now;	time(&now);		//将变量now的地址作为参数传递给time()函数
    
  • tm结构体

    • time_t是一个长整数,不符合人类的使用习惯,需要转换成tm结构体,tm结构体在<time.h>中声明,如下
    struct tm{
        int tm_year;	//年份,其值等于实际年份减去1900
        int tm_mon;		//月份,取值区间为[0,11]
        int tm_mday;	//日期,取值区间为[1,31]
        int tm_hour;	//时,[0,23]
        int tm_min;		//分,[0,59]
        int tm_sec;		//秒,[0,59]
        int tm_wday;	//星期,[0,6],0代表星期天
        int tm_yday;	//从每年的1月1日开始算起的天数,[0,365]
        int tm_isdst;	//夏令时标识符
    }
    
  • localtime()库函数

    • 用于把time_t表示的时间转换为tm结构体表示的时间
    • localtime()函数不是线程安全的,localtime_r()是线程安全的
    struct tm *localtime(const time_t *timep);
    sturct tm *localtime_r(const time_t *timep, struct tm *result);
    
  • maketime()库函数

    • 用于把tm结构体时间转换为time_t时间
    time_t mktime(struct tm *tm);
    
  • gettimeofday()库函数

    • 用于获取1970年1月1日到现在的秒和当前秒中已逝去的微妙数,可以用于程序的计时
    #include<sys/time.h>
    
    int gettimeofday(struct timeval *tv,struct timezone *tz);
    
    struct timeval{
    	time_t	tv_sec;			//1970-1-1到现在的秒数
        suseconds_t tv_usec;	//当前秒中已逝去的微秒
    };
    struct timezone{
        int tz_minuteswest;
        int tz_dsttime;
    }
    
  • 程序睡眠

    • 如果要把程序挂起一段时间,可以用sleep()和usleep()两个库函数
    #include<unistd.h>
    
    unsigned int sleep(unsigned int seconds);
    int usleep(useconds_t usec);
    
7.linux的目录操作
  • 获取当前工作目录
#include<unistd.h>

char *getcwd(char *buf, size_t size);
char *get_current_dir_name(void);	//malloc(),free()
  • 切换工作目录
#include<unistd.h>
int chdir(const char *path);	//0-成功;其他-失败(目录不存在或没有权限)
  • 创建目录
#include<sys/stat.h>
int mkdir(const char *pathname, mode_t mode);

//pathname-目录名
//mode-访问权限,如0755,不要省略前置的0(八进制数)
//0-成功;其他-失败(上级目录不存在或没有权限),上级目录必须存在
  • 删除目录
#include<unistd.h>
int rmdir(const char *path);
//path-目录名
//0-成功;其他-失败(目录不存在或没有权限)
  • 获取目录中文件的列表
#include<dirent.h.

DIR *opendir(const char *pathname);	//打开目录
struct dirent *readdir(DIR *dirp)l	//读取目录
int closedir(DIR *dirp);			//关闭目录
DIR*目录指针变量名

struct dirent{
	long d_ino;			//索引节点号
    off_t d_off;		//在目录文件中的偏移
    unsigned short d_reclen;	//文件名长度
    unsigned char d_type;		//文件类型,其中8表示常规文件,4表示目录
    char d_name[NAME_MAX+1];	//文件名,最长255字符
}
8.linux的系统错误
  • 整型全局变量errno,存放了函数调用过程中产生的错误代码
#include<errno.h>

char *strerror(int errnum);		//非线程安全
int strerror_r(int errnum,char *buf,size_t buflen);	//线程安全
  • perror(),用于在控制台显示最近一次系统错误的详细信息
#include<stdio.h>
void perror(const char *s);
  • 不属于系统调用的函数不会设置errno,属于系统调用的函数才会设置
  • errno不能作为调用库函数失败的标志,它只有在库函数调用发生错误时才会被设置,当库函数调用成功时,errno的值不会被修改,不会主动的置为0,一般在返回值是失败的情况下,才关注errno
9.目录和文件的更多操作
  • access()函数用于判断当前用户对目录或文件的存取权限
#include <unistd.h>
int access(const char *pathname, int mode);
//满足mode权限返回0,-1-不满足,errno被设置,实际开发中,主要用于判断目录或文件是否存在
  • stat结构体用于存放目录或文件的详细信息
#include<sys/stat.h>
int stat(const char *path, struct stat *buf);	//获取path指定的目录或文件的详细信息,保存到buf结构体中,0-成功,-1失败,errno被设置
  • utime()修改目录或文件的时间
  • rename()用于重命名目录或文件,相当于操作系统的mv命令
  • remove()用于删除目录或文件,相当于操作系统的rm命令
10.linux的信号
  • 信号是软件中断,是进程之间相互传递消息的一种方法,用于通知进程发生了事件,但是,不能给进程传递任何数据

    • 信号产生的原因有很多,在shell中,可以用kill和killall命令发送信号,缺省15信号
      • kill -信号的类型 进程编号
      • killall -信号的类型 进程名
  • 信号的类型

    • -0,可以检测程序是否存活
    • -14,闹钟,定时
  • 信号的处理

    • 对该信号的处理采用系统的默认操作,大部分的信号的默认操作时终止进程
    • 设置中断的处理函数,收到信号后,由该函数来处理,signal()函数
    • 忽略某个信号,对该信号不做任何处理,就像未发生过一样
  • 发送信号

    • 在程序中,可以用kill()函数向其它进程发送信号
    • int kill(pid_t pid, int sig);,将sig指定的信号给参数pid指定的进程
      • pid>0,将信号传给进程号pid的进程
      • =0,将信号传给和当前进程相同进程组的所有进程,常用于父进程给子进程发送信号,注意,发送信号者进程也会收到自己发出的信号
      • =-1,将信号广播传送给系统内所有的进程,例如系统关机时,会向所有的登录窗口广播关机信息
      • 准备发送的信号代码,假如其值为0则没有任何信号送出,但是系统会执行错误检查,通常会利用sig值为0来检验某个进程是否仍在运行
11.进程中止
  • 终止进程的方式:

    • 在main()函数用return()
    • 在任意函数中调用exit()
    • 在任意函数中调用_exit()或_Exit()
    • 最后一个线程从启动历程(线程主函数)用return()
    • 在最后一个线程中调用pthread_exit()
    • 异常终止:
      • 调用abort()函数中止
      • 接收到一个信号
      • 最后一个线程对取消请求做出响应
  • shell中echo $?查看终止状态,如果进程被异常终止,终止状态为非0

  • 资源释放问题

    • return表示函数返回,会调用局部对象的析构函数,main函数中的return还会调用全局对象的析构函数
    • exit表示终止进程,不会调用局部对象的析构函数,只调用全局对象的析构函数
    • exit会执行清理工作,然后退出,_exit和_Exit直接退出,不会执行清理工作
  • 进程的终止函数

    • 进程可以用atexit()函数登记终止函数(最多32个),这些函数将由exit()自动调用
    • exit调用终止函数的顺序与登记时相反
    int atexit(void(*function)(void));
    
12.调用可执行程序

Linux提供了system()函数和exec函数族,在c++程序中,可以执行其他的程序(二进制文件、操作系统命令或shell脚本)

  • system会返回
  • exec,新进程的进程编号与原进程相同,但是,新进程取代了原进程的代码段、数据段和堆栈,不会返回
13.创建进程
  • linux的0、1、2号进程

    • 整个Linux系统全部的进程是一个树形结构

    • 0号进程(系统进程)是所有进程的祖先,它创建了1号和2号进程

    • 1号进程(systemd)负责执行内核的初始化工作和进行系统配置

    • 2号进程负责所有内核线程的调度和管理

    • 用pstree命令可以查看进程树(apt -y install psmisc)

    pstree -p 进程编号
    
  • 进程标识

    • 每个进程都有一个非负整数表示唯一的进程ID,虽然是唯一的,但是进程ID可以复用。当一个进程终止后,其进程ID就成了复用的候选者。linux采用延迟复用算法,让新建进程的ID不同于最近终止的进程所使用的ID。这样防止了新进程被误认为是使用了同一ID的某个已终止的进程。
    pid_t getpid(void)l;	//获取当前进程的ID
    pid_t getppid(void);	//获取父进程的ID
    
  • fork()函数

    • 一个现有的进程可以调用fork函数创建一个新的进程
    • 由该函数创建的新进程被称为子进程
    • 该函数被调用一次,但返回两次,子进程返回的是0,父进程返回的是新建子进程的进程ID,可以根据id执行不同的代码
    • 子进程和父进程继续执行fork()之后的代码,子进程是父进程的副本
    • 子进程获得了父进程数据空间、堆、栈的副本(虚拟地址,只是看起来相同,物理地址是不同的),不是共享
    • fork()之后,父进程和子进程的执行顺序是不确定的
    pid_t fork(void);
    
  • fork()的两种用法

    • 父进程复制自己,然后,父进程和子进程分别执行不同的代码。这种用法在网络服务中很常见,父进程等待客户端的连接请求,当请求到达时,父进程调用fork(),让子进程处理这些请求,而父进程则继续等待下一个连接请求
    • 进程要执行另一个程序。这种用法在shell中很常见,子进程从fork()返回后立即调用exec
  • 共享文件

    • fork()的一个特性是在父进程中打开的文件描述符都会被复制到子进程中,父进程和子进程共享一个文件偏移量
    • 如果父进程和子进程写同一描述符指向的文件,但又没有任何形式的同步,那么它们的输出可能会相互混合(解决方法:进程同步)
  • vfork()函数

    • vfork()函数的调用和返回值与fork()相同,但两者的语义不同
    • vfork()函数用于创建一个新进程,而该新进程的目的是exec一个新程序,它不复制父进程的地址空间,因为子进程会立即调用exec,于是也就不会使用父进程的地址空间。如果子进程使用了父进程的地址空间,可能会带来未知的结果
    • vfork()保证子进程先运行,在子进程调用exec或exit()之后父进程才恢复运行
14.僵尸进程
  • 如果父进程比子进程先退出,子进程将被1号进程托管(这也是一种让程序在后台运行的方法,一种是程序名后加&)

  • 如果子进程比父进程先退出,而父进程没有处理子进程退出的信息,那么,子进程将成为僵尸进程

  • 内核为每个子进程保留了一个数据结构,包括进程编号、终止状态、使用cpu时间等。父进程如果处理了子进程退出的信息,内核就会释放这个数据结构,父进程如果没有处理子进程退出的信息,内核就不会释放这个数据结构,子进程的进程编号将一直被占用。系统可用的进程编号是有限的,如果产生了大量的僵尸进程,将因为没有可用的进程编号而导致系统不能产生新的进程

  • 避免僵尸进程:

    • 子进程退出的时候,内核会向父进程发头SIGCHLD信号,如果父进程用signal(SIGCHLD,SIG_GN)通知内核,表示自己对子进程的退出不感兴趣,那么子进程退出后会立即释放数据结构
    • 父进程通过wait()/waitpid()等函数等待子进程结束,在子进程退出之前,父进程将阻塞等待
    • 如果父进程很忙,可以捕获SIGCHLD信号,在信号处理函数中调用wait()/waitpid()
15.多进程与信号
  • 在多进程的服务程序中,如果子进程收到退出信号,子进程自行退出,如果父进程收到退出信号,应该先向全部的子进程发送退出信号,然后自己再退出
16.共享内存
  • 多线程共享进程的地址空间,如果多个线程需要访问同一块内存,用全局变量就可以了
  • 在多进程中,每个进程的地址空间是独立的,不共享的,如果多个进程需要访问同一块内存,不能用全局变量,只能用共享内存
  • 共享内存允许多个进程(不要求进程之间有血缘关系)访问同一块内存空间,是多个进程之间共享和传递数据最高效的方式。进程可以将共享内存连接到它们自己的地址空间中,如果某个进程修改了共享内存中的数据,其它进程读到的数据也将会改变
  • 共享内存并未提供锁机制,也就是说,在某一个进程对共享内存进行读/写的时候,不会阻止其他进程对它的读/写。如果要对共享内存的读/写加锁,可以使用信号量
  • Linux中提供了一组函数用于操作共享内存,ipcs -m查看共享内存
    • shmget函数,用于创建/获取共享内存
    • shmat函数,用于把共享内存连接到当前进程的地址空间
    • shmdt函数,用于将共享内存从当前进程中分离,相当于shnat的反操作
    • shmctl函数,用于操作共享内存,最常用的操作是删除共享内存
    • 用root创建的共享内存,不管创建的权限是什么,普通用户无法删除
    • 在堆区分配的内存不属于共享内存如string
17.循环队列
  • 共享内存不能自动扩展,只能采用c++内置的数据类型
  • 共享内存不能采用stl容器,也不能使用移动语义
  • 如果要实现多进程的生产/消费者模型,只能采用循环队列
  • 21
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值