一、库
1、 什么是库?
1:库是一种加密的二进制文件
2:需要被操作系统载入内存里去使用
3:相比于可执行程序,它不可以直接运行
4:window和linux都有自己的库,但是两者是不兼容的
5:linux系统下的库有两种:(1)静态库(2)共享库(又叫动态库)
比如: 静态库 动态库
linux *.a *.so
window *.lib *.dll
2、静态库的制作和使用
1.制作:(这里的xxx是文件的名字)
gcc-c xxx.c -o xxx.o
ar-crs libxxx.a xxx.o
静态库的命名规范:
1. 必须以lib开头,
2. 紧跟库的名字,
3.跟扩展名 .a
4.例如: libxxx.a
2.使用:
gcc main.c -L路径 -lxxx
-L:指定静态库所在的目录
-l:指定静态库的名字,XXX部分
3.运行:
./a.out
优点:
a.out 运行后不需要库,可以直接运行
缺点:
每个a.out都要包含库,体积较大, 浪费资源; 对程序更新,部署,发布带来麻烦;
3、动态库的制作和使用
1、制作:
gcc -fPIC xxx.c -o xxx.o
gcc -shared -o libxxx.so xxx.o
动态库的命名规范:
必须以lib开头,紧跟库的名字,跟扩展名 .so
例如: libxxx.so
2、使用:
gcc main.c -L路径 -lxxx
ldd a.out //用于查看可执行程序依赖的动态库有哪些
3、运行:
./a.out
特点:在编译时不会链接到可执行文件中,只是再其中保存一个索引,在运行时,才真正的链接(动态),因此可执行程序体积小。
优点: a.out 体积较小, 节约资源; 只需要修改.so动态库,有利于程序的更新,部署,发布;
缺点:a.out 运行后需要库,不能直接运行。
二、进程
1、何为进程:
进程是一个程序的一次执行的过程
每一个进程都分配一个虚拟的4G内存
0-3G : 用户 ;3G-4G : 内核
2、进程和程序的区别:
(1):进程是动态的。
(2):程序是静态的。
3、进程的内存管理:
(1):正文段
(2):用户数据段
(3):内存段
4、进程号(PID):
每一个进程的唯一的一个标识号
5、与进程的交互:
ctrl+z / jobs -l / bg / fg / kill -l /kill -9 PID /ps -ajx
ctrl + z : 把前台进程转成后台进程
jobs -l :查看作业 11871:作业号
bg % 作业号 :把后台作业重新放在前台来,而且不能ctrl+c结束
fg % 作业号 :把后台作业重新放在前台来,能被ctrl+c结束
kill -l :查看信号的种类
kill -9 PID: 杀死该进程
ps -ajx: 查看进程
6、进程的运行状态:通过命令 ps -ajx 来查询
运行态R:此时进程或者正在进行,或者准备运行 内核调度程序到CPU上执行 running
等待态:此时进程需要满足一些条件,如果不满足就等待 可中断S:如果进程收到信号会醒来 ctrl+c 不可中断D:如果进程收到信号不会醒来
停止态T:此时进程被中止SIGSTOP
死亡态Z:已终止的进程、僵尸进程、即使已经是终止但还是向量数组中占有一个task_struct结构体
7、优先级:
< 高优先级
N 低优先级L 有些页被锁进内存
s 会话组组长+ 位于前台的进程组
l 多线程,克隆线程
1、相关函数
1.fork
函数原型:
#include <unistd.h>
pid_t pid; //进程号
pid_t fork(void);
功能:创建进程
参数:无
返回值:
pid < 0 创建进程失败
pid == 0 子进程
pid > 0 父进程
代码演示:
#include <stdio.h>
8 #include <unistd.h>
9
10 int main(int argc, char *argv[])
11 {
12 pid_t pid;
13 pid = fork();
14 while(1){
15 if(pid < 0){//判断进程是否创建成功
16 perror("fork");
17 return -1;
18 }else if(pid == 0){//如果PID==0则创建子进程
19 printf("son is runing..\n");
20 sleep(1);
21 }else{
22 printf("father is runing..\n");//如果PID != 0则创建子进程
23 sleep(1);
24 }
25 }
26
27 return 0;
28 }
运行结果:
在运行结果上我们可以看到两个进程都成功得跑了起来。
从fork函数往下分为两个进程开始运行。 父进程和子进程执行顺序是随机的。
fork函数特性:
1.子进程创建时,几乎拷贝了父进程全部内容,包括代码段、数据段、堆栈段、文件描述符、虚拟地址空间
2.同一个父进程创建的子进程都是属于同一个进程组 pkill -9 -g PGID
3.进程是管理资源的最小单位
2、exit
函数原型:
#include <stdlib.h>
void exit(int status); //有缓存
//系统调用函数
#include <unistd.h>
void _exit(int status); //无缓存
代码演示:
#include <stdio.h>
8 #include <stdlib.h>
9 #include <unistd.h>
10 void fun(void)
11 {
12 printf("世界你好!");//因为exit是有缓存的,所以这里字符串就没有+'\n'
13 exit(0);
14 }
15 int main(int argc, char *argv[])
16 {
17 printf("你好世界!\n");
18
19 fun();
20
21 return 0;
22 }
运行结果:
无缓存的_exit代码演示:
#include <stdio.h>
8 #include <stdlib.h>
9 #include <unistd.h>
10 void fun(void)
11 {
12 printf("世界你好!");
13 _exit(0);
14 }
15 int main(int argc, char *argv[])
16 {
17 printf("你好世界!\n");
18
19 fun();
20
21 return 0;
22 }
运行结果演示:
因为无缓存,所以fun函数里的字符串打印不了。
3、wait
函数原型:
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
功能:父进程等待子进程结束,回收它的资源
参数:
status: 一般写NULL
WEXITSTATUS(status) 获取子进程返回值
WIFEXITED(status) 判断子进程是否正常结束
代码演示:
#include <stdio.h>
8 #include <stdlib.h>
9 #include <unistd.h>
10 #include <sys/types.h>
11 #include <sys/wait.h>
12
13 int main(int argc, char *argv[])
14 {
15 pid_t pid;
16 pid=fork();
17 if(pid < 0){
18 perror("fun");
19 return -1;
20 }else if(pid == 0){//子进程
21 int n=5;
22 while(n--){
23 printf("sun is runing...\n");
24 sleep(2);
25 }
26 exit(0);//结束子进程
27 }else{
28 wait(NULL);//父进程等待子进程结束,回收它的资源
29 while(1){
30 printf("father is runing...\n");
31 sleep(2);
32 }
33 }
34
35 return 0;
36 }
运行结果:
这里的子进程我们只让它循环5次,子进程进行的时候父进程是没有进行的因为父进程在等待wait函数,等子进程5次执行完并且结束掉自己后,父进程才开始循环。
子进程先与父进程退出---父进程未回收资源---子进程会变成僵尸进程
危害:占用进程号、内存空间、PCB进程控制块等
解决:wait / waitpid / 信号
注意:任何进程结束都会变成僵尸进程,只是时间有长有短
父进程先与子进程退出---子进程会变成孤儿进程---被init进程接管(收养)
init进程:系统启动后运行的第一个用户空间进程,pid=1,会定期扫描系统,收养孤儿进程。
注意:孤儿进程一般没什么危害
4、waitpid
函数原型:
pid_t waitpid(pid_t pid, int *status, int options);
功能:父进程自动回收子进程结束后的资源
参数:
pid: -1 任意进程
status: NULL
options: 0 阻塞
WNOHANG 非阻塞
eg: waitpid(-1, NULL, 0); == wait(NULL);
阻塞的话,就相当于上面的wait函数,而非阻塞则是父子进程同时运行,子进程结束后父进程自动回收其结束后的资源。
#include <stdio.h>
8 #include <unistd.h>
9 #include <stdlib.h>
10 #include <sys/types.h>
11 #include <sys/wait.h>
12
13 int main(int argc, char *argv[])
14 {
15 pid_t pid;
16 pid = fork();
17 while(1){
18 if(pid < 0){
19 perror("fork");
20 return -1;
21 }else if(pid == 0){
22 int n= 5;
23 while(n--){
24 printf("son is runing..\n");
25 sleep(2);
26 }
27 exit(0);//结束子进程
28 }else{
29 int n=5;
30 waitpid(-1,NULL,WNOHANG);//非阻塞,父进程与子进程同时运行待子进程结束后回收其资源。
31 while(n--){
32 printf("father is runing..\n");
33 sleep(2);
34 }
35 }
36 }
37
38 return 0;
39 }
运行结果演示:
5、exec函数族
1、概念:
函数族提供了一种在进程中启动另一个程序执行的方法。 它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段,在执行完之后,原调用进程的内容除了进程号外,其他全部被新程序的内容替换了。另外,这里的可执行文件既可以是二进制文件,也可以是Linux下任何可执行脚本文件。
比如bash用到了exec函数来执行我们的可执行文件。
2、在linux程序下使用exec函数族主要有一下两种情况:
当进程认为自己不能再为系统和用户做出任何贡献时,就可以调用任何exec 函数族让自己重生。
如果一个进程想执行另一个程序,那么它就可以调用fork函数新建一个进程,然后调用任何一个exec函数使子进程重生。
3、函数原型:
#include <unistd.h>
int execl(const char *path, const char *arg, ...);
int execv(const char *path, char *const argv[]);
int execlp(const char *file, const char *arg, ...);
int execvp(const char *file, char *const argv[]);
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execve(const char *path, char *const argv[], char *const envp[]);
返回值:
成功不返回
失败返回 -1 更新 errno
注意:
exec函数的参数表传递方式以函数名的第五位字母来区分:
字母为"l"(list)的表示逐个列举的方式;
字母为"v"(vertor)的表示将所有参数构造成指针数组传递;
以p结尾的函数可以只给出文件名
以"e"(enviromen)结尾的两个函数execle、execve就可以在
envp[]中设置当前进程所使用的环境变量
使用execle和execve可以自己向执行进程传递环境变量,但不会继承Shell进程的环境变量
代码演示:
#include <stdio.h>
8 #include <unistd.h>
9 int main(int argc, char *argv[])
10 {
11 printf("111111111111111\n");
12 int m = execl("./test","test",NULL);
13 if(m == -1){
14 perror("excel");
15 return -1;
16 }
17 printf("2222222222\n");
18 while(1);
19
20 return 0;
21 }
运行结果:
从结果我们可以看出,我们在04execl这个程序中运行了另一个程序,代码中的test是我的时间程序通过库制作生成的可执行文件,这里execl就执行了这个可执行文件,代码下的字符串 '222222222' 为什么没有打印出来呢,是因为我们test文件里的程序功能是循环打印当前时间。
事实上,这6个函数中真正的系统调用只有execve,其他5个都是库函数,它们最终都会调用execve这个系统调用
6、守护进程
1.守护进程: 在linux中与用户交互的界面叫终端,从终端运行起来的程序都依附于这个终端, 当终端关关闭时,相应的进程都会被关闭,守护进程可以突破这个限制。
2.特点: 在后台服务的进程 生存期很长 守护进程独立于控制终端 比如:init进程 pid=1 开机运行 关机才结束
3.守护进程创建流程:
1. 创建子进程,父进程退出 fork(void);
2. 在子进程中创建新会话 setsid(void);
3. 修改工作目录 chdir("");
4. 修改umask (增加安全性) umask();
5. 关闭文件描述(回收资源) close();
代码演示:
#include <stdio.h>
8 #include <unistd.h>
9 #include <unistd.h>
10 #include <time.h>
11 #include <stdlib.h>
12 #include <sys/types.h>
13 #include <sys/stat.h>
14
15 int main(int argc, char *argv[])
16 {
17 pid_t pid;
18 pid = fork();
19 while(1){
20 if(pid < 0){
21 perror("fork");
22 return -1;
23 }else if(pid == 0){ //子进程
24 setsid();//:2、创建新的会话
25 chdir("./");//:3、修改子工作目录,修改到当前目录
26 umask(0);//:4、修改umask值
27 close(0);
28 close(1);
29 close(2);//5、关闭文件描述符
30
31 FILE *fp;
32 while(1){
33 time_t t;
34 time(&t);
35 char *p = ctime(&t);
36 fp = fopen("./test.txt","a");//打开根目录下的test.txt文件
37 fputs(p,fp);//给test.txt文件写入地址p的内容
38 fflush(fp);//强制刷新
39 sleep(1);//延时1S
40 }
41 }else{
42 exit(0);//1、父进程退出
运行后关闭终端打开文件的结果:
隔一会再打开的结果:
按理来说关闭终端,则相应的进程都会被关闭,可这个test.txt文件还在不断地写入,此时此刻它已经成为了一个后台进程。
关闭后台进程:
关闭后再查看进程
此时这个我们自己设置的守护进程就被杀掉了。
三、线程
1.线程的概念:(同一个地址空间中的多个任务)
(1)共享相同虚拟地址空间的多个任务
(2)使用多线程大大提高了任务切换的效率
1、线程相关的函数:
1.pthread_create
线程的创建,函数原型:
#include <pthread.h>
pthread_t tid; //线程号
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
功能:创建线程
参数:
thread: &tid 线程号的地址
attr: NULL 线程函数默认的属性
start_routine: 线程函数的函数名
arg: 给线程函数传递的参数 NULL
返回值:
成功: 0
失败: -1
代码演示:
#include <stdio.h>
8 #include <pthread.h>
9 #include <unistd.h>
10 void *fun(void *arg)
11 {
12 while(1){
13 printf("世界你好!\n");
14 sleep(2);
15 }
16 }
17 int main(int argc, char *argv[])
18 {
19 pthread_t tid;
20 int ret=pthread_create(&tid,NULL,fun,NULL);
21 if(ret == -1){
22 perror("pthread_create");
23 return -1;
24 }
25 while(1){
26 printf("你好世界!\n");
27 sleep(2);
28 }
29
30 return 0;
31 }
代码运行结果:
从运行结果来看,1个线程就创建完毕了,一个在主函数下的是进程也就是主线程,另一个就是程序里的fun1函数。
2、pthread_join/pthread_detach
回收线程资源函数,函数原型:
int pthread_join(pthread_t thread, void **retval);
功能:主线程等待子线程结束,然后回收资源
参数:
thread: tid
retval: NULL (接收子线程结束的返回值)
返回值:
成功:0
失败:-1
int pthread_detach(pthread_t thread);
功能:主线程和子线程分离开来,系统自动回收资源 :非阻塞
参数:
thread: tid
返回值:
成功:0
失败: -1
两个回收函数,一个是阻塞的,一个则是非阻塞的
pthread_join代码演示:
#include <stdio.h>
8 #include <pthread.h>
9 #include <unistd.h>
10 void *fun(void *arg)
11 {
12 int n=5;
13 while(n--){
14 printf("世界你好!\n");
15 sleep(2);
16 }
17 }
18 int main(int argc, char *argv[])
19 {
20 pthread_t tid;
21 int ret=pthread_create(&tid,NULL,fun,NULL);
22 if(ret == -1){
23 perror("pthread_create");
24 return -1;
25 }
26 pthread_join(tid,NULL);//等待子线程结束回收资源,然后运行主线程
27 while(1){
28 printf("你好世界!\n");
29 sleep(2);
30 }
31
32 return 0;
33 }
运行结果演示:
因为子线程在里我们只让它循环5次,循环时pthread_join函数便阻塞在主线程前,等子线程结束后就回收掉它的资源,执行主线程。
pthread_detach代码演示:
#include <stdio.h>
8 #include <pthread.h>
9 #include <unistd.h>
10 void *fun(void *arg)
11 {
12 int n=5;
13 while(n--){
14 printf("世界你好!\n");
15 sleep(2);
16 }
17 }
18 int main(int argc, char *argv[])
19 {
20 pthread_t tid;
21 int ret=pthread_create(&tid,NULL,fun,NULL);
22 if(ret == -1){
23 perror("pthread_create");
24 return -1;
25 }
26 pthread_detach(tid);
27 while(1){
28 printf("你好世界!\n");
29 sleep(2);
30 }
31
32 return 0;
33 }
运行结果演示:
不阻塞,主线程和子线程同时进行,子线程结束后主线程自动回收资源。
3、pthread_cancel
关闭线程函数原型:
int pthread_cancel(pthread_t thread);
总结线程:
优点:线程间很容易进行通信,通过全局变量实现数据共享和交换。
缺点:多个线程同时访问共享对象时,需要引入同步和互斥机制。
2、线程的同步与互斥:
1、线程的同步------信号量:
函数原型sem_init:
#include <semaphore.h>
sem_t sem; //信号量的变量
int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:初始化信号量
参数:
sem: &sem
pshared: 0:线程 1:进程
value: 初值 0 or 1
返回值:
成功:0
失败:-1
int sem_wait(sem_t *sem); //-1
//如果初值为0,会阻塞
int sem_post(sem_t *sem); //+1
代码演示:
#include <stdio.h>
8 #include <unistd.h>
9 #include <semaphore.h>
10 #include <pthread.h>
11 sem_t sem;
12 void *fun(void *arg)
13 {
14 int n=5;
15 while(n--){
16 printf("真是美好的一天!\n");
17 sleep(2);
18 }
19 sem_post(&sem);//+1,初值为1不再阻塞,运行主线程
20 }
21 int main(int argc, char *argv[])
22 {
23 pthread_t tid;
24 sem_init(&sem,0,0);
25 pthread_create(&tid,NULL,fun,NULL);//创建线程
26 pthread_detach(tid);
27 printf("你好世界!\n");
28 sem_wait(&sem);//初值为0,阻塞,等待子线程运行。
29 printf("世界你好!\n");
30 while(1);
31 return 0;
32 }
演示结果:
程序运行,先打印你好世界,初始化信号量,遇到信号量sem_wait为0时阻塞,去执行子线程,执行到子线程里的sem_post后+1,不再阻塞,便去运行主线程。
总结线程的同步,多个任务按着我的理想要求来进行,谁1,谁2,然后一直12121212这样理想要求得执行下去。
2、线程的互斥----互斥锁
pthread_mutex_init函数原型:
pthread_mutex_t mutex; //互斥锁的变量
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
功能:初始化互斥锁
参数:
mutex: &mutex
mutexattr: NULL (默认属性)
返回值:
成功:0
失败:-1
int pthread_mutex_lock(pthread_mutex_t *mutex);
//加锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
//解锁
代码演示:
#include <stdio.h>
8 #include <pthread.h>
9 #include <unistd.h>
10
11 pthread_t tid1,tid2;
12 pthread_mutex_t mutex;
13 int n=5;
14 void *fun1(void *arg)
15 {
16 while(n--){
17 pthread_mutex_lock(&mutex);//加锁
18 printf("你好世界!\n");
19 sleep(2);
20 pthread_mutex_unlock(&mutex);//解锁
21 }
22 }
23 void *fun2(void *arg)
24 {
25 while(n--){
26 pthread_mutex_lock(&mutex);//加锁
27 printf("世界你好!\n");
28 sleep(2);
29 pthread_mutex_unlock(&mutex);//解锁
30 }
31 }
32 int main(int argc, char *argv[])
33 {
34 pthread_mutex_init(&mutex,NULL);
35 pthread_create(&tid1,NULL,fun1,NULL);//创建线程1
36 pthread_detach(tid1);
37 pthread_create(&tid2,NULL,fun2,NULL);//创建线程2
38 pthread_detach(tid2);
39 while(1);
40
41 return 0;
42 }
运行结果演示:
fun1和fun2都运用互斥锁,就先打印你好世界,再打印世界你好。