黑马linux系统编程网课
P1 linux命令
各种简单命令,tar find grep等
P17 vim命令与编译
-
跳转
-
跳转到66行
-
:66
-
66G
-
-
-
gg行首
-
G行尾
-
gg=G格式化
-
%跳到最后一个大括号
-
撤销
-
撤销 u
-
反撤销contrl r
-
-
dd删除一行
查看{d 宏定义
3K 查看当前函数的man
- 文本代码文件 .c
- gcc -E 预处理。处理宏定义,展开头文件。
- 展开头文件的文本代码文件 .i
- gcc -S 检查语法,编程汇编。得到的文件是.s
- 汇编指令 .s
- gcc -c变成机器代码.o
- 机器语言 .o
- 链接,数据段合并,地址回填
- 机器语言 .out
gcc -o是起名字的 gcc -I 指定头文件所在目录 -Wall所有警告都显示
编译消耗时间
|
|
-
gcc -S是检查, 要输出文件要加-o, 会生成文件
-
gcc -c 可以添加多个文件
-
静态库
-
静态库一般是.a文件
-
1 2 3 4 5 6 7 8
gcc -c first.c sencond.c #得到.o文件 ar -r lib库的名称.a first.o sencond.o #得到库文件 # 用rcs也行 gcc main.c -l库的名称 #使用 gcc main.c libname.a #库的.a文件要放在最后面 gcc main.c -lpcap -L../pcap10.2.1 -L是.a的所在路径的文件夹名 -l后加库名称, 一般库叫libxx.a, 名称就是xx
-
-
使用就是 gcc main.c 库的名称
-
-
动态库
-
制作后得到.so后缀
-
1 2 3 4 5 6 7
gcc -c -fpic add.c minus.c #得到.o文件 gcc -shared add.o minus.o lib库名称.so #得到库文件 gcc -shared -o lib库名称.so add.o minus.o #得到库 gcc main.c -l库的名称 -L/库所在的文件夹路径 #使用 gcc -o myprogram myprogram.c -L/path/to/libs -lexample #使用自带库 gcc -o myprogram myprogram.c -L/path/to/libs -lexample -Wl,-rpath=/path/to/libs #使用自己的库
-
-
动态库的使用,
- 要环境变量让动态链接库找到库,export= LD_LIBRARY_PATH=库的路径
- 把自己的库放在/lib里面也可以, 就会去/lib里面找了
- vim ~/.bashrc 是脚本文件, 得重新启动才能好使
- 把库的路径放到/etc/ld.so.conf里面
- 并生效, ldconfig -v
- v的意思是展示给用户看的, tar -zcvf也是一样
- 并生效, ldconfig -v
-
ldd a.out
- 查看运行时加载的动态库在哪
-
-
查看自己有什么库
-
1
gcc -print-search-dirs | grep libraries
- 1
-
P37 markfile
- 一个规则
- 两个函数
- 三个自动变量
|
|
-
变量的定义
-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
cpp := main.cpp #常量, 后续不能更改 obj := main.o cpp = main.cpp #变量, 可以更改 obj = main.o cpp ?= main.cpp #如果之前有定义, 则不动, 如果没有赋值, 会赋值 obj ?= main.o ${obj} : ${cpp} #用变量用${} @g++ -c ${cpp} -o ${obj} 目标文件的完整名称 可以用$@ 第一个依赖 可以用$< 所有依赖文件 可以用$^
- 12
-
-
函数的使用
-
1 2
cpp := $(subst <from>, <to>, <list>) #字符串替换 cpp := $(patsubst <from>/%.c, <to>/%.o, <list>) #字符串替换
- 处理字符串
-
1 2 3 4 5
#-I头文件路径 include_paths := /usr/include \ /usr/include/opencv2/core include_paths := $(foreach item, $(include_paths), -I$(item)) #循环 I_flag := $(include_paths:%=-I%) #简单方法
- 1
-
1 2 3
objs/%.o : src/%.cpp @mkdir -p $(dir $@) # $@是目标文件完整名字 @gcc -c $^ -o $@
- 编译出目标文件
-
1
filter-out(<delet>,<from>)
- 可以用通配符来过滤不想要的文件
-
P50
write()函数写到文件里,会有一个缓冲区,操作系统自带的
- 操作系统书里写过,要有一个缓冲区,写满后一起放到物理存储设备中。
操作系统利用PID(Process Identifier)找到PCB(Process Control Block)。
- 每个进程都有自己的唯一的PID,通过PID找到PCB
- PCB的本质是结构体,这个结构体里面有一个指针,指向文件描述符表,这个表是键值对的,可能是红黑树做的。
- 表中 0 表示标准输入(stdin),1 表示标准输出(stdout),2 表示标准错误输出(stderr),而其他非负整数值表示其他打开的文件。
- 因为默认这个表是1024,所以一个进程只能打开1024-3个文件。
dentry 和 inode 。
- dentry就是dirent结构体,里面有文件名和指针等,指针指向inode结构体。
- inode里面有文件权限,大小,位置的指针,指针指向文件的磁盘地址。
- opendir()返回一个dir指针
- readir()返回一个dirent的结构体指针。输入参数是dir指针。
如果把目录文件的权限改成不同的rwx
-
a-r就不能读了
-
a-w就不能在目录里添加文件了
-
a-x就不能cd进入目录了
lseek()函数偏移量,返回从头到光标的偏移量
- off_t lseek(int fd, off_t offset, int whence);
- whence起始位置
- SEEK_SET 从头
- SEEK_CUR 现在为止
- SEEK_END 结尾
查看文件属性
- int stat(const char *restrict path, struct stat *restrict buf);
- 穿透查看文件各种属性的
- int lstat(const char *restrict path, struct stat *restrict buf);
- 不穿透查看文件各种属性
用buf.st_size()获取文件大小
用buf.st_mode()获取文件类型
Man 2 stat 里面有写,用宏获取文件类型,参数m就是buf.st_mode()。
|
|
- 这章作业,总结以上所学的,递归实现遍历文件夹。
- 已经实现
P68
重定向
- int dup(int oldfd)
- 创建一个文件描述符(根据红黑树,就是选最小的数的文件描述),来指向参数里fildes所指向的文件
- int dup2(int oldfd, int newfd);
- 先判断旧的文件描述符valid(有效的),
- 如果不valid,返回失败
- 如果valid,返回新的文件描述符的数字
- 成功的话,新描述符就指向旧文件描述符的指向
- newfd的数值可以自己指定
- 先判断旧的文件描述符valid(有效的),
PCB进程控制块
- 进程id(PID),进程的唯一标志符
- 进程的状态
- 初始
- 就绪
- 运行
- 挂起(等待资源)
- 结束
- 进程切换时的寄存器里的内容
- MMU,描述虚拟内容地址空间的信息
- 描述控制终端的信息
- 当前工作目录
- 文件描述符
- 和信号相关信息
- 用户id和组id
- 环境变量和main函数的命令行参数
环境变量
- echo $PATH 显示环境变量路径
- echo $SHELL 显示当前shell
- echo $TERM 显示当前终端
- echo $LANG 显示语言,字符集
- echo $HOME 显示家目录
- env 显示所有环境变量
进程
- pid_t getpid(void) 获取自己的pid
- pid_t getppid(void) 获取父进程pid
父子进程
- 读时共享,写时复制
- 不同的是
- PID
- 返回值
- 进程创建时间
- 闹钟
- 未决信号集
- 共享的是
- 文件描述符
- mmap映射区
- fork()之后,父进程还是子进程哪个先执行不知道
P87
gdb调试
- fork()之后会出现子进程,设置跟踪哪个进程
- set follow-fork-mode parent
- set follow-fork-mode child
exec函数族
- 用了这个命令后,整个进程都会变成执行的进程,全部都改变了,变成了执行文件的模样
- int execl(const char *path, const char *arg, …)
- path 指定路径
- arg 指定命令,后面的是参数
- int execlp(const char *file, const char *arg, …)
- file系统调用,只要名字就行了
- arg 指定,后面是参数
- int execv(const char *path, char *const argv[])
- 用一个数组接收参数(vector)
- int execvp(const char *file, char *const argv[])
- 用一个数组接收参数(vector)的同时
- 是使用环境变量查找的
- int execle(const char *path, const char *arg, …, char * const envp[])
- int execvpe(const char *file, char *const argv[],char *const envp[])
- 这个函数族的参数代表
- l(list) 命令行参数列表
- p(path) 搜索file时用的path变量
- v(vector) 使用命令行参数数组
- e(environment) 使用环境变量数组
- 用这个函数的时候记得参数后面加NULL, 要不然函数不知道你在哪结束, 这不是c++.
- excve()是系统调用,上面都是用这个实现的
孤儿进程
- 父亲死了,儿子就会进孤儿院,init进程下
- 可以用kill杀掉
- 儿子死了,父亲还在,儿子就变成僵尸进程了
- 儿子就剩一个pcb表,里面有死亡原因.
- 不可以用kill杀掉
wait()函数
- pid_t wait(int *status) 会阻塞等待子进程死亡
- status描述子进程状态
- WIFEXITED(status) 子进程正常终止返回真
- WEXITSTATUS(status) 返回子进程的返回值
- WIFSIGNALED(status) 返回子进程被信号终止
- WTERMSIG(status) 输出是什么信号终止的
- 成功返回孩子的pid
- 失败返回-1
- wait(NULL) 不关心子进程的状态
- status描述子进程状态
- pid_t waitpid(pid_t pid, int *status, int options)
- 一次调用只能回收一个
- pid
- -1时 任意一个都可以回收
- >0 指定一个pid 就回收这个pid子进程
- 0 回收当前一个组的所有子进程
- <0 指定一个-的pid,那就代表那个组
- options
- WNOHANG 不阻塞
- \返回值>0时 回收子进程pid
- ==0时 并且 options是WNOHANG时,没成功
- \返回值<0时 失败
- 所有异常情况终止,都是由于信号.
- kill -l 可以看到所有信号 -9就是信号终止
管道
- 伪文件, 只在内存中有数据,不在存储里
- 管道
- socket
- 大小是4k,环形队列实现
- 读取后,管道里就没数据了
- 半双工
- int pipe(int fildes[2])
- fildes[0]读取
- fildes[1]写入
- 读管道
- 有数据就读
- 没数据
- 写入端还能不能写了
- 不能的话 返回0
- 能的话 阻塞等待
- 写入端还能不能写了
- 写管道
- 读端被关闭了,那就异常终止
- 没有全被关闭
- 管道满了,阻塞等待
- 没满,那就写入,返回成功写入的字节
- 进程间通信方式
- 信号
- 管道
- mmap映射
- socket
有一道习题写出ps | wc -l
- 这道题挺有意思, 找了一会bug,找到了,就是读管道的时候一直阻塞等待,因为没有一个control d返回信号,导致出不了结果, 怎么能让他出来那个结束信号,就是让管道不能再写入了,把能写入的信号全关了, wc命令就停止阻塞读取了.
P106进程间通信
- FIFO
- int mkfifo(const char *pathname, mode_t mode)
- 本质就是创建一个文件, 性质和管道一样
- mkfifo()
- open()
- writ()/read()
- close()
- int mkfifo(const char *pathname, mode_t mode)
- 文件共享
- 通过共同读或写一个文件实现共享数据
- 存储映射(MMAP)
- void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off)
- addr 通常NULL
- length 内存映射区大小
- prot 内存映射区的读写属性
- flags 标志共享内存属性 共享还是私有(意识是是否能从内存保存回储存)
- fd 用于创建内存映射区的那个文件的 文件描述符
- off 偏移位置.要是4k的整数倍 默认是0
- 返回值
- 成功 映射区的受地址
- 失败 MAP_FAILED , -1
- 创建后,可以把文件描述符可以关了, 用指针读写
- ftruncate(fd, 4) 拓展文件大小
- void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off)
P124信号
信号量
-
产生
-
按键产生
-
系统调用产生
-
软件条件产生
-
硬件异常产生
-
命令产生
-
-
概念
- 未决 产生与递达之间的状态, 就是pcb控制块已经是1了, 但还没有处理
- 处理有三种方式, 系统默认, 屏蔽字屏蔽, 捕捉(自定义)
- 递达 达到系统了,并在处理
- 未决 产生与递达之间的状态, 就是pcb控制块已经是1了, 但还没有处理
变量三要素
- 名称
- 类型
- 数值
信号四要素
- 编号
- 名称
- 事件
- 默认处理动作
linux常规信号
- sighup 退出终端发的信号
- sigh control+c
- sigquit control+/
- sigill
- sigtrap
- sigabrt
- sigbus 总线错误
- sigfpe 除0操作
- sigkill 就是终止信号
- siguse1
- 段错误
-
- control + z 挂起
|
|
实际时间 = 用户时间 + 内核时间 + 等待时间(I/O)
|
|
|
|
关于屏蔽信号集 信号屏蔽字mask
|
|
|
|
|
|
信号被捕捉期间会执行函数, 再有信号来的话, 会屏蔽, 并且多个信号来的话, 只会入队一个
P143会话与线程
进程组和会话
- 多个进程组是会话
- 进程组id是父进程id
- 会话id是
- ps ajx 查看进程
- PPID(Parent Process ID)
- PID(Process ID) 进程唯一key
- PGID(Process Group ID) 进程组ID
- SID(Session ID) 会话ID
- UID(User ID)是用于标识用户的唯一标识符。
|
|
-
关闭父进程,创建子进程
-
在子进程setsid()
-
改变当前工作目录
- 防止目录被卸载
-
重设文件掩码
- 022–755
- 345–432
- 4是r读
- 2是w写
- 1是x执行
- 三个是因为文件所有者、文件所属组和其他用户
-
关闭文件描述符
-
开始执行守护进程
-
进程组长不能创建会话
1
pid_t setsid(void)//返回sid 失败返回-1
-
会话id, 三个id相等
1 2 3
getpid() getpgid(0) getsid(0)
-
线程
-
进程
- 有独立的地址空间
- 有独立的PCB块
- 最小分配资源单位
- 可以看成一个线程的进程
-
线程
- 没有独立的地址空间
- 有独立的PCB块
- 最小执行单位
- 一个线程可以看成一个进程, 去抢夺cpu运行
-
ps -Lf 进程id
- 线程号
- 线程池
- 这个应该先是页目录, 然后页表, 然后页面, 最后是物理地址, 同时这个机构应该叫二级页表
-
线程共享资源
- 文件描述符
- 没中信号处理方式
- 当前工作目录
- 用户id和组id
- 内存地址空间
-
线程不共享资源
- 线程id
- 处理线程(内核栈)
- errno变量
- 信号屏蔽字
- 调度优先级
-
线程优点
- 提高程序并发
- 开销小
- 共享数据方便
- 堆区共享
- 动态分配的内存malloc new分配的
- 栈不共享
- 自己函数里定义的, 不共享
- 堆区共享
-
线程缺点
- 库函数, 不稳定
- 不能GDB调试
-
1 2
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg); //-pthread编译用
-
thread线程id
-
attr属性
-
1 2
int pthread_attr_init(pthread_attr_t *attr); //创建属性 int pthread_attr_destroy(pthread_attr_t *attr);//destroy属性
- 设置attr
- 成功返回0
- 用这个传出的attr, 当作下面函数的传入参数
-
1 2 3
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate); int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate); //设置attr属性
-
PTHREAD_CREATE_DETACHED 分离
-
PTHREAD_CREATE_JOINABLE 非分离
-
-
start_routine回调函数
-
arg 指定回调函数的传入参数
- 成功0
- 失败errno
-
-
1
pthread_t pthread_self(void);
- 返回本线程id
-
1 2 3
void pthread_exit(void *retval); /*return 退出函数 exit() 退出主进程*/
- 退出线程
-
1
int pthread_join(pthread_t thread, void **retval);//回收函数, 阻塞
-
thread 传入参数
-
retval 传出参数
-
成功返回0
-
-
1
int pthread_cancel(pthread_t thread)//杀死线程
- thread 传入参数
- 需要有契机才能被杀死, 就是有系统调用函数
-
1
void pthread_testcancel(void);
- 测试有没有pthread_cancel调用
-
1
int pthread_detach(pthread_t thread);//分离线程, 能自动回收资源
- thread 分离哪个线程
-
1
fprintf(stderr,"%s",strerror(ret));//打印出错信息
- 线程独有的出错检测
P165线程同步
锁是建议锁, 没有强制性, 应该在拿到数据前加锁
|
|
互斥锁
-
1 2 3 4 5
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); //动态初始化 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //静态初始化 int pthread_mutex_destroy(pthread_mutex_t *mutex);
- init锁, destroy锁
- restrict的意思是不能再复制出来一个指针
- attr 即将创建的互斥量属性, 默认NULL
-
1 2 3
int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex);//成功--, 失败返回.设置错误号EBUSY int pthread_mutex_unlock(pthread_mutex_t *mutex);
- 加锁解锁, 加不了锁的话会阻塞, 尝试锁不阻塞
-
1 2 3 4 5 6
/* 使用粒度越小越好, 使用完之后快速解锁;互斥锁本质是结构体, 看成整数便于理解 init()后把锁看成1 加锁-- 解锁++ */
读写锁
读共享, 写独占; 读写同时的话写锁权限高, 因为先读的话, 写会改变的, 没用
-
1 2 3 4 5
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); //动态初始化 pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; //静态初始化 int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
- 初始化
-
1 2 3
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
- 加锁
-
1
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock)
- 解锁
形成死锁
- 互斥条件(Mutual Exclusion)
- 至少有一个资源被标记为独占资源,即一次只能由一个进程(线程)使用。其他进程(线程)必须等待该资源的释放。
- 请求与保持条件(Hold and Wait)
- 一个进程(线程)在等待其他进程(线程)释放资源的同时,继续持有已经获得的资源。
- 不可剥夺条件(No Preemption)
- 已经分配给一个进程(线程)的资源不能被强制性地剥夺,只能由持有资源的进程(线程)显式地释放。
- 循环等待条件(Circular Wait)
- 存在一个进程(线程)的资源请求序列,每个进程(线程)都在等待下一个进程(线程)所持有的资源。
条件变量
-
1 2 3 4 5
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr); //动态初始化 pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //静态初始化 int pthread_cond_destroy(pthread_cond_t *cond);
- 初始化锁
-
1
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);//等待
- 解锁
- 等待
- 加锁
-
1 2
int pthread_cond_signal(pthread_cond_t *cond);//唤醒至少一个 int pthread_cond_broadcast(pthread_cond_t *cond);//唤醒所有
Reactor模型
信号量(semaphore)
相当于初始化为N的互斥量, 信号和信号量没关系, 可以应用在线程、 进程中同步
-
1 2
int sem_init(sem_t *sem, int pshared, unsigned int value); int sem_destroy(sem_t *sem);
- pshared
- 0–线程间同步
- 1–进程间、线程同步
- value(指定同时访问的线程数)
- pshared
-
1 2 3 4 5
int sem_wait(sem_t *sem); //--操作 int sem_trywait(sem_t *sem);//尝试--(不阻塞) int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);//绝对时间(absolutely), 1970.1.1 0:00 //公历就是耶稣诞生, 佛历从释迦摩尼诞生算 int sem_post(sem_t *sem);//++操作
- struct timespec *abs_timeout
- 秒
- 毫秒
- struct timespec *abs_timeout