库的制作与进线程

一、库

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都运用互斥锁,就先打印你好世界,再打印世界你好。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值