gcc编译:预处理 编译 汇编 连接
-I (大写 i )指定头文件所在目录位置
-c 预处理+编译+汇编 得到二进制文件
-g 编译时添加调试语句 主要支持gdb调试
-l(小写 l ) 指定动态库库名
-L 指定动态库路径
静态库生成 使用
1.生成函数的头文件、源文件
//头文件 add.h
int add(int a,int b);
//源文件 add.cpp
#include "add.h"
int add(int a,int b){
return a+b;
}
///sub...其他函数相同
2.编译生成目标文件
add.cpp、sub.cpp -》 add.o、sub.o
g++ -c add.cpp sub.pp
3.制作静态库 lib+库名.a ;汇编阶段把编译生成的文件转换成二进制目标代码 .o
ar rcs libmylib.a add.o sub.o
4.大型库中不知道单独的函数头文件,只能单独声明,所以再生成一个头文件mymath.h
!!这个头文件是由静态库作者提供,不是使用者
#ifndef _MYMATH_H_
#define _MYMATH_H_
int add(int ,int);
int sub(int ,int);
#endif
4.制作main函数调用add、sub等
//#include "add.h"
//#include "sub.h"
#include "mymath.h"//就可省略上面两个单独声明
#include <iostream>
using namespace std;
int main(){
int a=1;
int b=2;
cout<< add(a,b)<< sub(a,b);
}
5.使用静态库 ; -o:链接 将目标文件、启动代码、库文件链接成可执行文件main
g++ main.cpp libmylib.a -o main
6.执行文件
./main
动态库的制作(优先使用动态库)
1.生成函数的头文件、源文件(和静态库一样)
2.生成与位置无关目标文件
-fPIC生成的函数就和位置无关,挂上@plt标识,等待动态绑定
g++ -c add.cpp -o add.o -fPIC
3.制作动态库 lib+库名.so
g++ -shared -o libmylib.so add.o sub.o
4.使用动态库
-l+库名
-L+动态库.so文件所在位置
g++ main.cpp -o main -l mylib -L ./
5.指定动态库路径并生效,然后再执行文件(临时)
export LD_LIBRARY_PATH=./
关闭终端后会再次报错,所以
vi ~/.bashrc
然后把export LD_...写进去保存,最后使配置文件立即生效
. .bashrc
6.执行
./main
gdb(g++ debug)调试工具,需要-g调试语句生成的调试表,生成的可执行文件大小不一样
g++ hello.cpp -o hello -g
gdb hello
list 1
....
gdb分析逻辑错误
* 或者#跳转到函数定义
list / l 列出源码
b 20 在20行位置设置断点(20行没有执行)
run / r 运行程序停在断点
next / n 继续运行下一行程序(会跳过断点)
step / s 继续运行下一行程序(进入断点)
finfish 退出step
print / p n 打印变量n的值
ptype 变量数据类型
display i 跟踪变量
undiaplay 3 取消跟踪
continue 执行完毕断点后的代码
quit / q 退出调试
makefile
命名只能是m/Makefile 才能用make命令
目标:依赖条件
语句
makefile最终版本 模板 :方便后续的功能添加
src=$(wildcard ./*.c)//找到所有的cpp文件
//把所有cpp文件改为.o ,没有操作只是集合
//并且是带着地址的替换,所以要加上路径
obj=$(patsubst ./src/%.cpp , ./obj/%.o , $(src))
//告知最终目标
ALL:a.out
a.out:$(obj)
g++ $^ -o $@ // $^ 是把所有的依赖都取出
$(obj):./obj/%.o:./src/%.cpp //$(obj)静态模式
g++ -c $< -o $@ // $< 是把所有的依赖依次取出
//如果需要头文件 -I ./inc inc是头文件目录
//如果头文件和源文件在不同文件夹,
//那么main函数就要说明头文件位置 #include "../inc/head.h"
clean:
-rm -rf $(obj) a.out
//伪目标声明
.PHONY:ALL clean
2K跳转到命令manpage
open
open返回的fd是文件描述符
#include<unistd.h>
int fd=open("./路径/文件名", O_RDONLY//O_WRONLY//O_RDWR)
fd=-1 出错
#include<fcntl.h>
int fd=open("/路径/文件名",O_RDONLY|O_WRONLY|O_RDWR|O_CREAT|O_TRUNC,0664);
//文件权限是 文件权限 mode & ~umask
fd=-1 错误
close(fd)
read write
read是把fd里的数据读到buf中
#include<unistd.h>
int len=read(fd,buf,sizeof(buf));
len>0 读取的字节数
len=0 读到末尾
len=-1 查看errno
errno=EAGIN或EWOULDBLOCK,
说明read读取设备文件是非阻塞方式,且文件无数据
write是把buf中的数据写到fd中
#include<unistd.h>
int ret=write(fd,buf,len);
ret=-1 错误
ret 返回写入的字节数
系统调用 库函数
系统调用:read/write这块,每次写一个字节,会疯狂进行内核态和用户态的切换,所以非常耗时。
库函数:fgetc/fputc,有个缓冲区,4096,所以它并不是一个字节一个字节地写,内核和用户切换就比较少
预读入,缓输出机制。能使用库函数的地方就使用库函数。
标准IO函数自带用户缓冲区,系统调用无用户级缓冲。系统缓冲区是都有的。
lseek
写数据到文件后读写光标在末尾,如果需要读就把读写光标移到起始位置
#include<sys/types.h>
#include<unistd.h>
int len=lseek(fd,偏移量,SEEK_SET/SEEK_CUR/SEEK_END)
len 较起始位置偏移量
len=-1 失败
获取文件大小
int len=lseek(fd,0,SEEK_END);
更改文件大小
lseek更改需要引起IO操作
所以用truncate() and ftruncate(),大于len的部分删掉,小于len在文件尾部加空字节或文件空洞。
#include<unistd.h>
#include<sys/types.h>
int ret=truncate("/路径/文件名",len);
文件必须可写
int ret=ftruncate(fd,len);
fd必须是可写的
ret=0 成功
ret=-1 失败
stat、lstat
stat 会穿透, 如果读取的是链接,会直接穿透找到链接的文件
lstat 不会穿透
#include<sys/stat.h>
#include<unistd.h>
struct stat sbuf;//传出参数
int ret=stat("/path/filename",&sbuf)
int ret=lstat("/path/filename",&sbuf)
ret=0 成功
ret=-1 失败
dup、dup2
#include<unistd.h>
int ret=dup(oldfd);
int ret=dup2(oldfd,newfd);
ret:新文件描述符 成功
ret:-1 失败
文件是空,会正常写入write(newfd,buf,len);
文件是非空,文件open后,默认从文件头部开始写,写入时会覆盖原先的内容
fcntl
#include<unistd.h>
#include<fcntl.h>
int ret=fcntl(fd,F_DUPFD,0);
第三个参数是指定文件描述符,如果描述符被占用就系统分配最小的
ret 返回新的文件描述符
fork
子进程从fork后面开始执行
#include<unistd.h>
pid_t pid;
pid=fork();
pid=-1 创建子进程失败
pid=0 子进程
pid>0 父进程,pid是生成子进程的进程id
父子进程相同:堆栈、全局变量、环境变量、信号处理方式
父子进程共享:全局变量(读时共享,写时复制)、文件描述符、mmap映射区
父子进程不同:进程id、各自父进程、未决信号集
getpid、getppid
#include<sys/stat.h>
#include<unistd.h>
pid_t pid,ppid;
pid=getpid(); //当前进程id
ppid=getppid();//父进程进程id
exec函数
fork出子进程,让子进程去执行其他的任务,就用exec函数把子进程和任务链接起来
execlp多使用于系统指令 ;execl使用于自己的函数
#include<unistd.h>
//execlp需要配合PATH环境变量使用
int ret=execlp("函数名",argv[0],argv[1]... ,NULL);
//execl的路径使用绝对路径和相对路径都可以
int ret=execl("程序路径/函数名",argv[0],argv[1]... ,NULL);
execl("./mywife" ,"./mywife" ,NULL);
成功 无返回
失败 ret=-1
孤儿、僵尸
孤儿进程:父进程先结束,子进程后结束,子进程被init回收
僵尸进程:子进程结束,父进程没有回收的这段时间
每个子进程都有这个阶段,只是时间长短不一样
kill对僵尸进程无效,只能kill父进程,让init回收僵尸
子进程结束,残留资源pcb存在内核,进程回收就是回收pcb
wait、waitpid
wait任意回收子进程,阻塞回收
#include<sys/wait.h>
int status;//存放信息
pid_t pid;
pid=wait(&status);
成功 返回回收进程的id
失败 -1
获取子进程正常终止值
if(WIFEXITED(status)){//为真是子进程正常终止
cout<<WEXITSTATUS(status);//输出子进程的返回值
}
获取子进程非正常终止值
if(WIFSIGNALED(status)){//为真是子进程非正常终止
cout<<WTERMSIG(status);//输出导致终止的信号值
}
waitpid指定子进程回收,并且可以设置成非阻塞
#include<sys/wait.h>
int status;
pid_t pid,wpid;
wpid=waitpid(pid,&status,WNHANG);//WNHANG非阻塞
//0 是阻塞
pid>0 指定回收的子进程
pid=0 同组子进程
pid=-1 任意子进程
wpid >0 成功回收pid
wpid =-1 失败
wpid =0 函数调用时是WNHANG ,并且子进程没有结束
进程间通信
管道pipe
管道类似文件描述符的操作,数据只能一次读取,数据只能单向流动、半双工、血缘关系中使用
读写行为:
读端:1.管道有数据 read返回读到的字节数
2.管道无数据
1.无写端 返回0
2.有写端 read阻塞等待
写端:1.无读端 异常终止
2.有读端
1.管道数据未满 返回写出的字节数
2.管道数据满 阻塞等待
#include<unistd.h>
int fd[2];
int ret=pipe(fd);//fd[0]读 fd[1]写
ret=0 成功
ret=-1 失败
兄弟间进程通信,需要父进程关闭自己的读写端,保证数据单向流动
管道fifo
fifo解决pipe的单向通信和只能血缘关系通信的问题,打开同一个fifo文件即可通信
#include<sys/stat.h>
int ret=mkfifo("fifoname",0664);
int fd=open("fifoname",O_WRONLY);
int fd=open("fifoname",O_RDONLY);
类似文件描述符对fd进行读写操作
ret=0 成功
ret=-1 失败
遮挡效果
文件系统预设的默认权限: rw_ rw_ rw_ 666
umask ___ _w_ _w_ 022
最终文件默认权限 rw_ r__ r__ 644
mmap
映射之后直接对指针操作
读时共享,写时复制;并且可以重复读取,读取之后数据不会消失
#include<sys/man.h>
char* p=NULL;
int fd=open("texttemp",O_RDWR|O_CREAT|O_TRUNC,0664);
映射的文件一定是可读的
ftruncate(fd,20);//设置文件大小
int len=lseek(fd,0,SRRK_END);
强转指针类型(char*)mmap...
p=mmap(NULL,len,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
参数1 指定映射的位置,一般NULL自动分配
参数2 共享内存映射区大小 要求<=文件的实际大小
参数3 共享内存映射区的读写属性。
参数4 共享内存的共享属性。MAP_SHARED、MAP_PRIVATE
参数5 被映射的文件描述符
参数6 offset映射偏移量,默认0,映射文件全部内容;要求是4k整数倍
成功 p=映射区首地址
失败 p=MAP_FAILED
close(fd);
释放映射区
int ret=munmap(p,len);
成功 ret=0
失败 ret=-1
信号
简单;不能携带大量信息;一旦信号出现,优先处理信号;所有信号产生和处理都是由内核完成
信号屏蔽字:信号被屏蔽后,会一直处于未决状态
未决信号集:信号的处理状态,已经产生但是尚未处理
#include<signal.h>
int ret=kill(pid,signum);
指定id执行signum信号
pid> 0: 发送信号给指定进程
= 0:发送信号给跟调用kill函数的那个进程处于同一进程组的进程。
< -n:取绝对值,发送信号给该n所对应的进程组的所有组员。
= -1:发送信号给,有权限发送的所有进程。
成功 ret=0
失败 ret=-1
alarm
单次定时器,定时T秒后发送SIGALRM终止程序
#include<unistd.h>
alarm(T);
无错误现象
setitimer
周期定时时间,先alarm之后周期定时
#include<sys/time.h>
struct itimerval new_value;
struct itimerval {
struct timeval {
time_t tv_sec;/* seconds */
suseconds_t tv_usec;/* microseconds */
}
it_interval;--->周期定时秒数
struct timeval {
time_t tv_sec;
suseconds_t tv_usec;
}
it_value;--->第一次定时秒数
};
int ret=setitimer(ITIMER_REAL,&new_value,&old_value);
ITIMER_REAL:采用自然计时。——> SIGALRM
ITIMER_VIRTUAL:采用用户空间计时---> SIGVTALRM
ITIMER_PROF:采用内核+用户空间计时---> SIGPROF
成功 ret=0
失败 ret=-1
signal
信号集操作函数
sigset_t set;自定义信号集。
sigemptyset(sigset_t *set);清空信号集
sigfillset(sigset_t *set);全部置1
sigaddset(sigset_t *set, int signum);将一个信号添加到集合中
sigdelset(sigset_t *set, int signum);将一个信号从集合中移除
sigismember(const sigset_t *set,int signum);判断一个信号是否在集合中。在--》1,不在--》0
sigprocmask
设置、解除屏蔽字,信号加入屏蔽字之后就会阻塞
#include<signal.h>
sigset_t set;//自定义信号集
sigemptyset(&set);//先全部置0
sigaddset(&set,signum);//屏蔽signum信号
int ret=sigprocmask(SIG_BLOCK,&set,&oldset);
SIG_BLOCK:设置阻塞
SIG_UNBLOCK:取消阻塞
SIG_SETMASK:
用自定义set替换屏蔽字
sigpending
查看未决信号集
#include<signal.h>
sigset_t set;
int ret=sigpending(&set);
成功 ret=0;
失败 ret=-1
信号捕捉
当遇到signum信号时,执行func
#include<signal.h>
void func(int signo){....}
signal(signum,func);
会话
多个进程组的集合
调用进程不能是进程组组长,该进程变成新会话首进程
该进程成为一个新进程组的组长进程
建立新会话时,先调用fork,父进程终止,子进程调用setsid
getsid
获取会话id
#include<unistd.h>
pid_t sid,pid;
pid=getpid();
sid=getsid(pid);
成功 sid返回会话id
失败 返回-1 查看errno
setsid
建立会话
#include<unistd.h>
pid_t sid;
sid=setsid();
成功 创建会话,sid返回新会话id
失败 -1 errno
守护进程
daemon:不受用户登录注销影响,只能用kill终止
1、创建子进程fork()
2、父进程结束,子进程创建新会话setsid()
3、改变工作目录位置,防止目录被卸载chdir("目录位置")
4、重设文件权限掩码umask(0664)
5、关闭/重定向文件描述符
线程
进程:有独立的进程地址空间。有独立的pcb。分配资源的最小单位。
线程:没有独立的进程地址空间。有独立的pcb。cpu执行的最小单位。
pthread_create
创建线程
#include<pthread.h>
pthread_t p_id;
pthread_attr_t attr;
void* func(void* arg){
...
}
设置pthread_create的第二个参数
设为分离属性
pthread_attr_t attr;
pthread_attr_init(&attr);//初始化属性
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
int ret=pthread_create(&p_id,&attr,func,NULL);
参1:传出参数,表新创建的子线程id
参2:线程属性。传NULL表使用默认属性。
参3:子线程回调函数。创建成功,ptherad_create函数返回时,该函数会被自动调用。
参4:参3的参数,需要转换成(void*),注意精度。没有的话,传NULL
成功:0
失败:errno
销毁线程属性
pthread_attr_destroy(&attr);
pthread_self
获取线程id
#include<pthread.h>
pthread_t p_id;
p_id=pthread_self();
父子进程 全局变量,读时共享,写时复制
主、子线程 全局变量修改就全都变化
pthread_exit
线程中不能使用exit,否则会退出整个进程;
退出线程
#include<pthread.h>
int rtvl;
pthread_exit((void*)rtvl);
参数通常是NULL
pthread_join
指定回收t_id线程,并得到返回的值
#include<pthread.h>
pthread_t t_id;
int *retvl;
int ret=pthread_join(t_id,(void**)&retvl);
返回值:
成功:0
失败:!=0 errno
pthread_cancel
杀死指定的线程,但是需要线程进入内核工作,如果没有就要自己设置取消点
#include<pthread.h>
pthread_t t_id;
int ret=pthread_cancel(tid);
线程没有进入内核,手动设置取消点
pthread_testcancel();
pthread_detach
设置线程分离态,线程终止,会自动清理pcb,无需回收
也可创建线程时pthread_create设置分离态
设置之后,pthread_join不可回收
#include<pthread.h>
pthread_t p_id;
int ret=pthread_create(&p_id,NULL,func,NULL);
ret=pthread_detach(p_id);
成功 ret=0
失败 ret!=0 errno
strerror(ret)
锁
线程同步 :协同步调,对公共区域数据按序访问。
互斥锁
#include<pthread.h>
//动态初始化
pthread_mutex lock;//声明变量
int ret=pthread_mutex_init(&lock,NULL);
//静态初始化
pthread_mutex lock=PTHREAD_MUTEX_INITIALIZER;
//上锁 --
ret=pthread_mutex_lock(&lock);
线程操作数据
//解锁 ++
ret=pthread_mutex_unlock(&lock);
//销毁锁
ret=pthread_mutex_destroy(&lock);
条件变量
阻塞等待条件
pthread_cond_wait
1)阻塞等待条件变量满足
2)解锁已经加锁成功的信号量(相当于pthread_mutex_unlock(&mutex)),12两步为一个原子操作
3)当条件满足,函数返回时,解除阻塞并重新申请获取互斥锁。重新加锁信号量(相当于,pthread_mutex_lock(&mutex);)
#include<pthread.h>
//动态初始化 条件变量
pthread_cond_t cond;
pthread_cond_init(&cond,NULL);
//静态初始化 条件变量
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
//动态初始化 lock
pthread_mutex_t lock;
pthread_mutex_init(&lock,NULL);
//静态初始化 lock
pthread_mutex_t=PTHREAD_MUTEX_INITIALIZER;
消费者模式(多消费者要用while,不能用if)
while(head==NULL){
pthread_cond_wait(&cond,&lock);
//解锁,并阻塞等待
}
//收到信号后再上锁
线程处理操作
//解锁,让其他线程操作
pthread_mutex_unlock(&lock);
生产者模式
//上锁生产
pthread_mutex_lock(&lock);
//线程操作完毕 解锁
pthread_mutex_unlock(&lock);
//通知等待在该条件变量上的一个线程唤醒
pthread_cond_signal(&cond);
//唤醒阻塞在条件变量上的所有线程
pthread_cond_broadcast(&cond);
信号量
应用线程、进程间同步
初始化为N的互斥量
#include<semaphore.h>
//声明
sem_t sem;
int ret=sem_init(&sem, 0 , 5);
参数1 信号量
参数2 0:线程间同步
1:进程间同步
参数3 同时访问的数量
成功 ret=0
失败 ret=-1 errno
sem_wait(&sem); 调用一次就执行--操作 直到为0阻塞
sem_post(&sem); 调用一次就执行++操作 直到为5阻塞
sem_destroy(&sem); 销毁
信号量实现消费者、生产者
sem_t blank_num,star_num;
sem_init(&blank_num,0,5);
sem_init(&star_num,0,0);
生产者
sem_wait(&blank);
生产
sem_post(&star);
消费者
sem_wait(&star);
消费
sem_post(&blank);
销毁
sem_destroy(&blank);
sem_destroy(&star);