库
头文件:
#include<stdio.h>
<>代表区系统路径下查找头文件 /usr/include
#include"head.h"
""代表先去当前路径下查找头文件,找不到再去系统路径下查找
头文件也就是以.h结尾的文件,其中包含:宏定义、结构体、共用体和枚举的定义、函数声明、外部引用(extern)、其他头文件、重定义typedef、条件编译
补充:
1. auto //(自动型)
定义变量,一般省略会认为auto类型
2. static:修饰变量和函数 // (静态型)
修饰变量:
1) 变量的存放位置在全局区(静态区)
如果静态变量有初值,存放.data区,没有初值存放在.bss区域,赋初值为0
2) 生命周期为整个程序
3) 限制作用域:
修饰局部变量,和普通局部变量的作用域没有区别,但是生命周期被延长为整个程序。
修饰全局变量,限制在本文件中使用
4) 只初始化一次,初值赋值0。
修饰函数:
static修饰函数,限制本文件中使用
3. extern:外部引用
通过extern可以引用其他文件中的全局变量或函数
4. register:寄存器类型
五大区:
源文件:
包含main函数的xx.c
包含子函数的xx.c,封装的函数需要在头文件里放声明
库文件(不能包括main函数)
1. 库的定义
当使用别人的函数时除了包含头文件以外还可以用库文件。
头文件:函数声明、结构体、共用体、枚举的定义、宏定义、重定义、外部引用、其他头文件。
库:把一些常用的函数的目标文件打包在一起,提供相应的函数接口,便于程序员使用。本质上来说库是一种可执行代码的二进制形式文件。
由于windows和linux的本质不同,因此而这库的二进制是不兼容的。(Linux中的C运行库是glibc, 由GUN发布。)
2. 库的分类
静态库和动态库,本质的区别是代码被载入的时刻不同。
2.1 静态库
静态库在程序编译时会被复制到目标代码中,以.a结尾。
优点:程序运行时将不再需要该静态库,运行时无需加载库,运行速度更快,可移植性好。
缺点:静态库中的代码复制到了程序中,因此体积较大;静态库升级后,程序需要重新编译链接。
静态库的制作
(1) 将源文件编译生成目标文件
gcc -c fun.c -o fun.o
(2) 创建静态库用ar命令,将多个.o转换成.a
ar crs libfun.a fun.o
静态库文件名的命名规范是以lib为前缀,接着就是静态库的名字,扩展名为.a。
(3) 测试使用静态库: gcc
gcc main.c -L. -lfun //-L指定库的路径, -l指定库名
./a.out //执行可执行文件
#include <stdio.h>
#include <stdlib.h>
#include"head.h"
#define N 10int main(int argc, char const *argv[])
{sqelist_p p = CreateEpSeqlist(); // 创建顺序表堆区空间InsertIntoSeqlist(p, 0, 81);ShowSeqlist(p);InsertIntoSeqlist(p, 1, 30);ShowSeqlist(p);InsertIntoSeqlist(p, 2, 20);ShowSeqlist(p);InsertIntoSeqlist(p, 3, 70);ShowSeqlist(p);InsertIntoSeqlist(p, 1, 88);ShowSeqlist(p);deletintoseqlist(p, 2);ShowSeqlist(p);tihuanseqlist(p, 2, 100);ShowSeqlist(p);printf(" %d\n", chazhaosqelist(p, 1000));return 0;
}
#include<stdio.h>
#include<stdlib.h>
#include"head.h"
#define N 10
sqelist_p CreateEpSeqlist()
{sqelist_p p = (sqelist_p)malloc(sizeof(sqelist_t));if (p == NULL){perror("CreateEpSeqlist");return NULL;}p->last = -1; // 最后一个元素没有时下表为-1return p;
}int isfulllist(sqelist_p p) // 判满
{return p->last == N - 1;
}
int isepseqlist(sqelist_p p) // 判空
{return p->last == -1;
}
int deletintoseqlist(sqelist_p p, int post)
{if (isepseqlist(p) || post < 0 || post > p->last){printf("输入有误");return -1;}for (int i = post + 1; i <= p->last; i++){p->data[i - 1] = p->data[i];}p->last--;
}
int tihuanseqlist(sqelist_p p, int post, int bata)
{if (isepseqlist(p) || post < 0 || post > p->last){printf("输入有误");return -1;}p->data[post] = bata;
}
int chazhaosqelist(sqelist_p p, int bata)
{// if (isepseqlist(p) )// {// printf("输入有误\n");// return -1;// }for (int i = 0; i <= p->last; i++){if (p->data[i] == bata)// printf("查到查找结果");return i;}return -1;
}
int InsertIntoSeqlist(sqelist_p p, int post, int bata)
{if (isfulllist(p) || post < 0 || post > p->last + 1){printf("输入有误\n");return -1;}for (int i = p->last; i >= post; i--){p->data[i + 1] = p->data[i];}p->data[post] = bata;p->last++;return 0;
}
void ShowSeqlist(sqelist_p p)
{for (int i = 0; i <= p->last; i++){printf("%d ", p->data[i]);}putchar(10);
}
#define N 10
typedef struct sqelist
{int data[N];int last;
} sqelist_t, *sqelist_p;sqelist_p CreateEpSeqlist();
int isfulllist(sqelist_p p);
int isepseqlist(sqelist_p p);
int deletintoseqlist(sqelist_p p, int post);
int tihuanseqlist(sqelist_p p, int post, int bata);
int chazhaosqelist(sqelist_p p, int bata);
int InsertIntoSeqlist(sqelist_p p, int post, int bata);
void ShowSeqlist(sqelist_p p);
2.2 动态库
动态库是在程序运行的时候才被载入代码中。也叫共享库,以.so结尾。
优点:程序在执行时加载动态库,代码体积小;程序升级更简单。
不同应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例。
缺点:运行时还需要动态库的存在,移植性较差。运行速度慢。
动态库的制作
(1) 用gcc来创建共享库
gcc -fPIC -c fun.c -o fun.o //-fPIC创建与地址无关的编译程序
gcc -shared fun.o -o libmyfun.so //生成动态库
(2) 测试使用动态库
gcc main.c -L. -lmyfun
./a.out: 可以正常编译通过,但是运行时报错
error while loading shared libraries: libmyadd.so: cannot open shared object file: No such file or directory
原因:当加载动态库时,系统会默认从/lib或/usr/lib路径下查找库文件,所以不用-L加路径了, 直接gcc main.c -lmyfun 就可以了
实现:
./a.out: 可以正常编译通过,但是运行时报错
error while loading shared libraries: libmyadd.so: cannot open shared object file: No such file or directory
解决方法(有三种):
1) 把库拷贝到/usr/lib和/lib目录下。(此方法编译时不需要指定库的路径)
2) 在LD_LIBRARY_PATH环境变量中加上库所在路径。
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
(终端关闭,环境变量就没在了)
3) 添加/etc/ld.so.conf.d/*.conf文件。把库所在的路径加到文件末尾,并执行ldconfig刷新
sudo vi xx.conf
添加动态库存在的路径,如:
/home/hq/work/lib
总结静态库和动态库
静态库:编译阶段,体积大,移植性好,升级麻烦。
动态库:运行阶段,体积小,移植性差,升级简单。
升级演示:改变源文件,重新制作库
升级动态库:只需要修改源文件重新制作动态库,不需要重新编译链接了,因为是在执行的时候才加载库的代码。
升级静态库:需要重新编译链接
指令解析:
-L 路径:指定静态库的路径
-l(小写的L) 库名:指定库名
-I(大写i) 路径:指定头文件的路径,如果不指定默认的就是/usr/include
<> //从系统路径下查找头文件
"" //先从当前路径下查找头文件,没找到再去系统路径下查找
ldd 可执行文件名:查看链接的动态库
补充:
同名的静态库和动态库:默认优先使用动态库,如果想使用静态库 需要在后面加 -static,这是内核规定的。
如果链接没有lib前缀的库文件,可以直接用-指定库的全名无需加l选项。
进程 Process
1. 什么是进程
进程和程序的区别
1.1 概念
程序:编译好的可执行文件
存放在磁盘上的指令和数据的有序集合(文件)
程序是静态的,没有任何执行的概念。
进程:一个独立的可调度的任务
执行一个程序所分配的资源的总称
进程是程序的一次执行过程
进程是动态的,包括创建、调度、执行和消亡
1.4 进程分类
交互进程:该类进程是由shell控制和运行的。交互进程既可以在前台运行,也可以在后台运行。该类进程经常与用户进行交互,需要等待用户的输入,当接收到用户的输入后,该类进程会立刻响应,典型的交互式进程有:shell命令进程、文本编辑器等
批处理进程:该类进程不属于某个终端,它被提交到一个队列中以便顺序执行。(目前接触不到)
守护进程:该类进程在后台运行。它一般在Linux启动时开始执行,系统关闭时才结束。
1.5 进程状态
1)运行态(TASK_RUNNING):R
指正在被CPU运行或者就绪的状态。这样的进程被成为runnning进程。
2)睡眠态(等待态):
可中断睡眠态(TASK_INTERRUPTIBLE)S:处于等待状态中的进程,一旦被该进程等待的资源被释放,那么该进程就会进入运行状态。
不可中断睡眠态(TASK_UNINTERRUPTIBLE)D:该状态的进程只能用wake_up()函数唤醒。
3)暂停态(TASK_STOPPED):T
当进程收到信号SIGSTOP、SIGTSTP、SIGTTIN或SIGTTOU时就会进入暂停状态。可向其发送SIGCONT信号让进程转换到可运行状态。
4)死亡态:进程结束 X
5)僵尸态(TASK_ZOMBIE):Z 当进程已经终止运行,但还占用系统资源,要避免僵尸态的产生
l is multi-threaded (using CLONE_THREAD, like NPTL pthreads do)多线程
1.6 进程状态切换图
进程创建后,进程进入就绪态,当CPU调度到此进程时进入运行态,当时间片用完时,此进程会进入就绪态,如果此进程正在执行一些IO操作(阻塞操作)会进入阻塞态,完成IO操作(阻塞结束)后又可进入就绪态,等待CPU的调度,当进程运行结束即进入结束态。
什么是阻塞和非阻塞?
阻塞(blocking)、非阻塞(non-blocking):可以简单理解为需要做一件事能不能立即得到返回应答,如果不能立即获得返回,需要等待,那就阻塞了,在等待的过程中可以做其它事情。否则就可以理解为非阻塞。
1.7 进程相关命令
ps 查看系统中的进程
-aux:USER(用户) PID(进程id) %CPU %MEM VSZ RSS TTY(终端 ?:不依赖终端) STAT(状态) START TIME COMMAND PID:是唯一标识id号
ps -ef:查看父进程ID:PPID
top动态显示系统中运行的进程
< high-priority(not nice to other users)高优先级
N low-priority(nice to other users)低优先级
+ is in the foreground process group前台进程
前后台进程切换:
./a.out &:将a.out在后台运行
fg 编号:将后台运行的进程拉到前台运行
ctrl z:将前台运行的进程暂停同时放在后台
bg 编号:将后台暂停的进程在后台运行起来
renice 改变正在运行进程的优先级
Nice值通常是一个整数,在大多数操作系统中,它的范围通常是-20到+19。较小的Nice值表示进程的优先级较高,而较大的Nice值表示进程的优先级较低
NI:优先级 +19 ~ -20 值越小,优先级越高
PR:20 + NI
nice 以指定优先级启动进程
jobs 查看当前终端后台的进程
查看时:须ctrl+z 暂停程序,在进程中维持挂起状态(补:Ctrl+C :强制中断程序,程序无论运行哪里都停止)获取进程号 jobs+进程号
kill 给进程发送信号
kill -l:查看linux下的信号
2) SIGINT ctrl+c 结束进程
3) SIGQUIT 退出
4) SIGILL 结束进程
9) SIGKILL 杀死进程 不可忽略信号
10) SIGUSR1 未定义功能
12) SIGUSR2 未定义功能
13) SIGPIPE 管道破裂信号
14) SIGALRM 时钟信号
17) SIGCHLD 子进程状态发生改变会给父进程发送
18) SIGSTOP 停止 不可忽略信号
kill num PID:给指定的进程发送num信号
补充:优先级调度
根据进程的优先级进行调度,优先级高的进程先执行。
两种类型:
1. 非剥夺式(非抢占式)优先级调度算法。当一个进程正在处理上运行时,即使有某个更为重要或紧迫的进程进入就绪队列,仍然让正在进行的进程继续运行,直到由于其自身原因而主动让出处理机(任务完成或等待事件),才把处理机分配给更为重要或紧迫的进程。
2. 剥夺式(抢占式)优先级调度算法。当一个进程正在处理机上运行时,若有某个更为重要或紧迫的进程进入就绪队列,则立即暂停正在运行的进程,将处理机分配给更重要或紧迫的进程。
2. 进程函数接口
2.1创建进程fork()
pid_t fork(void);
功能:创建子进程
返回值:
成功:在父进程中:返回子进程的进程号 >0
在子进程中:返回值为0
失败:-1并设置errno
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{pid_t pid;pid = fork();if (pid == -1){perror("fork err");return -1;}else if (pid = 0){printf("i am child process\n");while (1); // 让子进程不要结束}else{printf("i am parent prosess\n");while (1); // 让父进程不要结束}return 0;
}
解释:./a.out会启动一个进程,执行到fork()函数时会在当前进程中创造了一个子进程并把代码以及数据信息拷贝到子进程,这两个进程只有个别数据例如进程号不一样,此时这两个进程由CPU随机调度。注意!!子进程会得到fork函数返回值然后执行fork之后的代码,fork函数之前的代码不会执行。
子进程的pid号比其父进程大1
特点:
1)子进程几乎拷贝了父进程的全部内容。包括代码、数据、系统数据段中的pc值、栈中的数据、父进程中打开的文件等;但它们的PID、PPID是不同的。
2)父子进程有独立的地址空间,互不影响;当在相应的进程中改变全局变量、静态变量,都互不影响。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{int a = 1;pid_t pid;pid = fork();if (pid == -1){perror("fork err");return -1;}else if (pid == 0){printf("[child] %d %p\n", a, &a);}else{a = 100;printf("[parent] %d %p\n", a, &a);}while (1); // 让进程不要结束return 0;
}
3)若父进程先结束,子进程成为孤儿进程,被init进程收养,子进程变成后台进程。
4)若子进程先结束,父进程如果没有及时回收资源,子进程变成僵尸进程(要避免僵尸进程产生)