【1】库
1.定义:本质上来说库是一种可执行代码的二进制形式;
通俗讲就是把一些常用函数的目标文件打包在一起,提供相应
函数的接口,便于程序员使用;它可以被操作系统载入内存执行。
由于windows和linux的本质不同,因此二者库的二进制是不兼容的
2.库的分类:
1)静态库:静态库在程序编译时会被连接到目标代码中,
程序运行时将不再需要该静态库,因此体积较大。
2)动态库在程序编译时并不会被连接到目标代码中,
而是在程序运行时才被载入,因此在程序运行时还需要动态库
存在,因此代码体积较小
3.静态库的制作步骤:
1-将源文件编译生成目标文件
gcc -c add.c -o add.o
2-创建静态库用ar命令,它将很多.o转换成.a
ar crs libmyadd.a add.o
静态库文件名的命名规范是以lib为前缀,紧接着跟静态库名,扩展名为.a
3-使用静态链接库
gcc main.c -L. -lmyadd
4-执行./a.out
优缺点:
优点: 程序中已f包含代码,运行时不再需要静态库。
运行时无需加载库,运行速度更快。
缺点: 静态库中的代码复制到了程序中,使程序会占更多的磁盘和内存空间
静态库升级后,程序需要重新编译链接
4.动态库的制作步骤:
1-我们用gcc来创建共享库
gcc -fPIC -c hello.c -o hello.o
-fPIC 创建与地址无关的编译程序
gcc -shared -o libmyhello.so hello.o
2-编译代码
gcc main.c -L. -lmyhello
ldd a.out 查看a.out的运行依赖那些库。
3-为了让执行程序顺利找到动态库,有三种方法 :
(1)把库拷贝到/usr/lib和/lib目录下。
(2)在LD_LIBRARY_PATH环境变量中加上库所在路径。
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. (最好是绝对路径,要在一行)
(终端关闭,环境变量就没在了)
本次配置只在当前终端有效,关闭和关机后就没效了。
想要永久有效需要到 .bashrc文件中进行修改。
(3) 添加/etc/ld.so.conf.d/*.conf文件,
把库所在的路径加到文件末尾,并执行ldconfig刷新
sudo vi xx.conf
添加动态库存在的路径,如:
/home/linux/19072/IO/day3/
优缺点:
优点: 程序在执行时加载动态库,代码体积小
将一些程序升级变得简单。
不同的应用程序如果调用相同的库,那么在内存
里只需要有一份该共享库的实例。
缺点:运行时还需要动态库的存在,移植性较差
【2】进程:
1.进程和程序的区别
程序:编译好的可执行文件
存放在磁盘上的指令和数据的有序集合(文件)
程序是静态的,没有任何执行的概念
进程:一个独立的可调度的任务
执行一个程序所分配的资源的总称
进程是程序的一次执行过程
进程是动态的,包括创建、调度、执行和消亡
2.进程包含三个段:
1)“数据段”存放的是全局变量、常数以及动态数据分配的数据空间
(如malloc函数取得的空间)等。
2)“正文段”存放的是程序中的代码
3)“堆栈段”存放的是函数的返回地址、函数的参数以及程序中的局部变量
3.进程的类型:
1)交互进程:交互进程既可以在前台运行,也可以在后台运行。
该类进程经常与用户进行交互,需要等待用户的输入,当接收到用户的输入后,
该类进程会立刻响应。
2)批处理进程:该类进程不属于某个终端,它被提交到一个队列中以便顺序执行
3)守护进程:该类进程在后台运行。它一般在Linux启动时开始执行,系统关闭时才结束。
4.进程的运行状态:
1)运行态:此时进程或者正在运行,或者准备运行。
2)等待态:此时进程在等待一个事件的发生或某种系统资源。
可中断:处在这种状态下的进程可以被信号中断,接收到信号或被显示地唤醒呼叫,唤醒之后,进程将转变为运行态。
不可中断:它不会处理信号,只有在它所等待的事件发生时,进程才被显示的唤醒
3)停止态:此时进程被中止。
4)死亡态:这是一个已终止的进程,但还在进程向量数组中占有一个task_struct结构。
D uninterruptible sleep (usually IO)(不可中断的等待态)
R running or runnable (on run queue)(运行态)
S interruptible sleep (可中断的等待态)
T stopped, either by a job control signal or because it is
being traced.(停止态)
X dead (should never be seen)(死亡态)
Z defunct ("zombie") process, terminated but not reaped by its
parent.(僵尸态)
For BSD formats and when the stat keyword is used, additional
characters may be displayed:
< high-priority (not nice to other users)(高优先级)
N low-priority (nice to other users)(低优先级)
s is a session leader(会话组组长)
l is multi-threaded (线程)
+ is in the foreground process group.(前台)
空:表示后台
【3】相关命令:
1.ps aux:
2.top:
shift+‘>’:向下查找
shift+‘<’:向上查找
q:退出
3.nice:按用户指定的优先级运行进程
PR=NI+20 NI [-20,19]数字越小优先级越高
nice -n 5 ./a.out
4.renice:改变正在运行进程的优先级
renice 5 PID
5.kill:发送信号(包括后台进程)
kill -num PID 给指定的进程,发送一个信号。
kill -l 查看进程信号
2) SIGINT 停止信号,默认杀死进程。ctrl + c
3) SIGQUIT 退出信号,默认也是杀死进程。 ctrl + \
9) SIGKILL 杀死进程,不能被忽略,不能被捕捉
14) SIGALRM 闹钟信号,默认也是杀死进程
17)SIGCHLD 儿子状态改变,内核会给它的父亲发送此信号
18) SIGCONT 唤醒信号,唤醒之后变为后台运行
19) SIGSTOP 暂停信号, 不能忽略,不能被捕捉
20) SIGTSTP 暂停信号, ctrl + z
补充:killall 进程名 :结束进程
6.bg 将挂起的进程在后台执行
bg 编号
7.fg 把后台运行的进程放到前台运行
fg 编号
8.jobs 查看所有的后台进程 :
【4】创建进程函数:
pid_t fork(void);
返回值:
失败:-1
成功:
0 :在子进程中
>0: 在父进程中返回的是子进程的进程号
特点:
1)fork函数是用来创建进程的,fork之后产生了两个进程
,每个进程都会有返回值,所以父进程中返回的是子进程
的进程号(>0);在子进程中返回0
2)子进程几乎拷贝了父进程的全部内容。包括代码、
数据、系统数据段中的pc值、栈中的数据、父进程中打开
的文件等;但它们的PID、PPID是不同的。
3)父子进程有独立的地址空间,互不影响;当在相应的
进程中改变全局变量、静态变量,都互不影响。
4)若父进程先结束,子进程成为孤儿进程,被init进程
收养,子进程变成后台进程。
5)若子进程先结束,父进程如果没有及时回收,子进程
变成僵尸进程(要避免僵尸进程产生)
注意:子进程复制了父进程的全部,虚拟地址空间,fork之前的代码复制
之后的代码复制并执行。
【5】获取进程号
pid_t getpid(void);
功能:获取当前进程的进程号
pid_t getppid(void);
功能:获取当前进程的父进程号
这两个函数,只要进程产生就绝对会调用成功。
补充:
一个进程最多能打开1024个文件描述符。
父进程和子进程可以通过同一个文件描述符对同一个文件进行操作。
(将文件描述符复制一份,操作的是同一个文件指针,同一个文件)
【6】进程退出函数
void exit(int status);
功能:结束进程,刷新缓存
void _exit(int status);
功能:结束进程,不刷新缓存
status是一个整型的参数,可以利用这个参数传递进程
结束时的状态。
通常0表示正常结束;
其他的数值表示出现了错误,进程非正常结束。
在实际编程时,可以用wait系统调用接收子进程的返回值
,进行相应的处理。
【7】进程回收函数
pid_t wait(int *status);
功能:阻塞等待子进程退出,回收资源(回收僵尸进程)
参数:status是一个整型指针,指向的对象用来保存
子进程退出时的状态。
? status若为空,表示忽略子进程退出时的状态
? status若不为空,表示保存子进程退出时的状态
另外,子进程的结束状态可由Linux中一些特定的宏来测定。
返回值:成功:子进程的进程号
失败:-1
WIFEXITED(*status) 判断子进程是否正常结束//wifexited
//正常结束返回1,非正常结束返回0。
WEXITSTATUS(*status) 获取子进程返回值//wexitstatus
//拿到的是一个8位的数,占一个字节,
//如:exit(10)为10,exit(-10),打印的结果为246
//若return的值,只是高八位表示。
提示:
调用该函数使进程阻塞(谁调用谁阻塞等待),直到任一个子进程结束或者
是该进程接收到了一个信号为止。如果该进程没有子进程
或者其子进程已经结束,wait函数会立即返回。
pid_t waitpid(pid_t pid, int *status, int options);
功能:阻塞等待子进程退出,回收资源
参数:pid:>0 指定子进程进程号
=-1任意子进程
=0 等待其组ID等于调用进程的组ID的任一子进程
<-1等待其组ID等于pid的绝对值的任一子进程
status:同上
options:WNOHANG:不阻塞
0:阻塞
返回值:正常:结束的子进程的进程号
使用选项WNOHANG且没有子进程结束时:0
出错:-1
waitpid(-1,NULL,0) ==> wait(NULL);
【5】exec函数族:
1.概念:
exec函数族提供了一种在进程中启动另一个程序执行的方法。
它可以根据指定的文件名或目录名找到可执行文件,并用它
来取代原调用进程的数据段、代码段和堆栈段。在执行完之后,
原调用进程的内容除了进程号外,其他全部都被替换了。
2.何时使用exec?
1)当进程认为自己不能再为系统和用户做出任何贡献了时就可以
调用exec函数,让自己执行新的程序
2)如果某个进程想同时执行另一个程序,它就可以调用fork函数
创建子进程,然后在子进程中调用任何一个exec函数。
这样看起来就好像通过执行应用程序而产生了一个新进程一样
3.相关函数:
int execl(const char *path, const char *arg, ...);
int execv(const char *path, 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[]);
int execlp(const char *file, const char *arg, ...);
int execvp(const char *file, char *const argv[]);
l : list列表,参数要以列表形式,展现出来。
p : path路径,从系统环境变量中去找可执行文件
v : 向量数组,将列表的参数装到数组中去。
e : 函数传递指定参数envp,允许改变子进程的环境,无后缀e时,子进程使用当前程序的环境。envp也是一个以NULL结尾的字符串数组指针
4.使用区别:
1)可执行文件查找方式
无p:指定完整的文件目录路径,
有p:只给出文件名,系统会自动从环境变量“$PATH”所包含的路径中进行查找。
2)参数表传递方式
有l:表示逐个列举的方式;
有v:表示将所有参数构造成指针数组传递,其语法为char *const argv[]
3)环境变量的使用
exec函数族可以默认使用系统的环境变量,也可以传入指定的环境变量。
这里,以“e”(Enviromen)结尾的两个函数execle、execve就可以在envp[]中传递当前进程所使用的环境变量,一般为NULL即可
1) #include <stdio.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
char * envbuf[100]={"usr=lisi","PWD=/home/sky","PATH=/bin:/usr/bin:/sbin/:",NULL};
int ret = execle("./a.out", "a.out", "hello","world",NULL, envbuf);
//char *arg[]={"a.out","asdf","asdf",NULL};
//int ret = execve("./a.out",arg,envbuf);
if(ret == -1) {
perror("execle");
return -1;
}
return 0;
}
2) #include <stdio.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
execlp("printenv", "printenv", NULL);
//char *buf[2] = {"printenv", NULL};
//execvp("printenv", buf);
return 0;
}
#include <stdio.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
char *args[] = {"ls", "-l", "-a", "-h", NULL};
int ret = execvp("ls", args);
if(ret == -1) {
perror("execlp");
return -1;
}
return 0;
}
【6】守护进程
//好的守护进程无法杀死,会自动重启
守护进程: daemon
什么是守护进程:
在linux中与用户交互的界面叫终端,从终端
运行起来的程序都依附于这个终端,当终端关
关闭时,相应的进程都会被关闭。
守护进程可以突破这个限制
守护进程的创建步骤:
1. 创建子进程,父进程退出
fork
2. 在子进程中创建新会话 //脱离终端
setsid
3. 改变当前目录为根目录
chdir
4. 重设文件权限掩码
umask
5. 关闭文件描述符
close
作业:
1. 复习今天所学内容
2. 通过父子进程完成文件io对文件的拷贝cp,父进程从文件开始
到文件的一半开始拷贝,子进程从文件的一半到文件末尾。
fd_src = open源文件
fd_dest = open目的文件
size = lseek();
size=size/2;
pid = fork();
if(pid == 0)
{
lseek(fd_src, size);
lseek(fd_dest, size);
while(1)
{
read(fd_src);
write(fd_dest);
}
}else{
lseek(fd_src, size);
lseek(fd_dest, size);
while(1)
{
read(,,5);
write();
}
}
close();
//6