linux系统编程

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);

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值