牛客网linux服务器课程
程序是包含一系列信息的文件,这些信息描述了如何在运行时创建一个进程:
二进制格式标识:每个程序文件都包含用于描述可执行文件格式的元信息。内核利用此信息来解释文件中的其他信息。(ELF可执行连接格式)
机器语言指令:对程序算法进行编码。
程序入口地址:标识程序开始执行时的起始指令位置。
数据:程序文件包含的变量初始值和程序使用的字面量值(比如字符串)。
符号表及重定位表:描述程序中函数和变量的位置及名称。这些表格有多重用途,其中包括调试和运行时的符号解析(动态链接)。
共享库和动态链接信息:程序文件所包含的一些字段,列出了程序运行时需要使用的共享库,以及加载共享库的动态连接器的路径名。
其他信息:程序文件还包含许多其他信息,用以描述如何创建进程。
进程是正在运行的程序的实例。是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
可以用一个程序来创建多个进程,进程是由内核定义的抽象实体,并为该实体分配用以执行程序的各项系统资源。从内核的角度看,进程由用户内存空间和一系列内核数据结构组成,

为了管理进程,内核必须对每个进程所做的事情进行清楚的描述。内核为每个进程分配一个 PCB(Processing Control Block)进程控制块,维护进程相关的信息,Linux 内核的进程控制块是 task_struct 结构体。
在/usr/src/linux-headers-xxx/include/linux/sched.h 文件中可以查
看struct task_struct结构体定义。其内部成员有很多,我们只需要掌握以下
部分即可:



ps aux / ajx
a:显示终端上的所有进程,包括其他用户的进程
u:显示进程的详细信息
x:显示没有控制终端的进程
j:列出与作业控制相关的信息

STAT参数意义:
D 不可中断 Uninterruptible(usually IO)
R 正在运行,或在队列中的进程
S(大写) 处于休眠状态
T 停止或被追踪
Z 僵尸进程
W 进入内存交换(从内核2.6开始无效)
X 死掉的进程
< 高优先级
N 低优先级
s 包含子进程
+ 位于前台的进程组

top
可以在使用 top 命令时加上 -d 来指定显示信息更新的时间间隔,在 top 命令
执行后,可以按以下按键对显示的结果进行排序:M 根据内存使用量排序P 根据 CPU 占有率排序T 根据进程运行时间长短排序U 根据用户名来筛选进程K 输入指定的 PID 杀死进程
kill [-signal] pid
kill –l 列出所有信号
kill –SIGKILL 进程ID
kill -9 进程ID
killall name 根据进程名杀死进程


0~ 32767。进程号总是唯一的,但可以重用。当一个进程终止后,其进程号就可以再次使用。 pid_t getpid(void);pid_t getppid(void);pid_t getpgid(pid_t pid);
系统允许一个进程创建新进程,新进程即为子进程,子进程还可以创建新的子进程,形成
进程树结构模型。
#include
#include
pid_t fork(void);
返回值:成功:子进程中返回 0,父进程中返回子进程 ID失败:返回 -1
失败的两个主要原因:
1. 当前系统的进程数已经达到了系统规定的上限,这时 errno 的值被设置
为 EAGAIN
2. 系统内存不足,这时 errno 的值被设置为 ENOMEM
#include #include pid_t fork(void);函数的作用:用于创建子进程。返回值:fork()的返回值会返回两次。一次是在父进程中,一次是在子进程中。在父进程中返回创建的子进程的ID,在子进程中返回0如何区分父进程和子进程:通过fork的返回值。在父进程中返回-1,表示创建子进程失败,并且设置errno父子进程之间的关系:区别:1.fork()函数的返回值不同父进程中: >0 返回的子进程的ID子进程中: =02.pcb中的一些数据当前的进程的id pid当前的进程的父进程的id ppid信号集共同点:某些状态下:子进程刚被创建出来,还没有执行任何的写数据的操作- 用户区的数据- 文件描述符表父子进程对变量是不是共享的?- 刚开始的时候,是一样的,共享的。如果修改了数据,不共享了。- 读时共享(子进程被创建,两个进程没有做任何的写的操作),写时拷贝。
#include
#include
#include int main() {int num = 10; // 栈空间的量,在各自进程中都有栈处理,互不干扰// 创建子进程pid_t pid = fork();// 判断是父进程还是子进程if(pid > 0) {// printf("pid : %d\n", pid);// 如果大于0,返回的是创建的子进程的进程号,当前是父进程printf("i am parent process, pid : %d, ppid : %d\n", getpid(), getppid());printf("parent num : %d\n", num);num += 10; printf("parent num += 10 : %d\n", num);} else if(pid == 0) {// 当前是子进程printf("i am child process, pid : %d, ppid : %d\n", getpid(),getppid());printf("child num : %d\n", num);num += 100;printf("child num += 100 : %d\n", num);}// for循环-父子进程交替执行(子进程继承了父进程的资源)for(int i = 0; i < 3; i++) { printf("i : %d , pid : %d\n", i , getpid());sleep(1);}return 0;
}/*
实际上,更准确来说,Linux 的 fork() 使用是通过写时拷贝 (copy- on-write) 实现。
写时拷贝是一种可以推迟甚至避免拷贝数据的技术。
内核此时并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间。
只用在需要写入的时候才会复制地址空间,从而使各个进行拥有各自的地址空间。
也就是说,资源的复制是在需要写入的时候才会进行,在此之前,只有以只读方式共享。
注意:fork之后父子进程共享文件,
fork产生的子进程与父进程相同的文件文件描述符指向相同的文件表,引用计数增加,共享文件偏移指针。
*/


fock创建子进程
实际上,更准确来说,Linux 的 fork() 使用是通过写时拷贝 (copy- on-write) 实现。
写时拷贝是一种可以推迟甚至避免拷贝数据的技术。
内核此时并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间。
只用在需要写入的时候才会复制地址空间,从而使各个进行拥有各自的地址空间。
也就是说,资源的复制是在需要写入的时候才会进行,在此之前,只有以只读方式共享。
注意:fork之后父子进程共享文件,
fork产生的子进程与父进程相同的文件文件描述符指向相同的文件表,引用计数增加,共享文件偏移指针。

父子进程之间的关系:区别:1.fork()函数的返回值不同父进程中: >0 返回的子进程的ID子进程中: =02.pcb中的一些数据当前的进程的id pid当前的进程的父进程的id ppid信号集共同点:某些状态下:子进程刚被创建出来,还没有执行任何的写数据的操作- 用户区的数据- 文件描述符表父子进程对变量是不是共享的?- 刚开始的时候,是一样的,共享的。如果修改了数据,不共享了。- 读时共享(子进程被创建,两个进程没有做任何的写的操作),写时拷贝。
查看调试父进程还是子进程 show follow-fork-mode
设置调试父进程或者子进程:set follow-fork-mode [parent(默认) | child]

设置调试模式:set detach-on-fork [on | off]
on,表示调试当前进程的时候,其它的进程继续运行,查看调试的进程: info inferiors
切换当前调试的进程: inferior id

使进程脱离 GDB 调试 : detach inferiors id

exec 函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件。

exec 函数族的函数执行成功后不会返回,

l(list) 参数地址列表,以空指针结尾
v(vector) 存有各参数地址的指针数组的地址
p(path) 按 PATH 环境变量指定的目录搜索可执行文件
e(environment) 存有环境变量字符串地址的指针数组的地址
execlint execl(const char *path, const char *arg, .../* (char *) NULL */);
/* #include int execl(const char *path, const char *arg, ...);- 参数:- path:需要指定的执行的文件的路径或者名称a.out /home/nowcoder/a.out 推荐使用绝对路径./a.out hello world- arg:是执行可执行文件所需要的参数列表第一个参数一般没有什么作用,为了方便,一般写的是执行的程序的名称从第二个参数开始往后,就是程序执行所需要的的参数列表。参数最后需要以NULL结束(哨兵)- 返回值:只有当调用失败,才会有返回值,返回-1,并且设置errno如果调用成功,没有返回值。*/
#include
#include int main() {// 创建一个子进程,在子进程中执行exec函数族中的函数pid_t pid = fork();if(pid > 0) {// 父进程printf("i am parent process, pid : %d\n",getpid());sleep(1);}else if(pid == 0) {// 子进程// execl("hello","hello",NULL); 在子进程中执行hello程序execl("/bin/ps", "ps", "aux", NULL); // 执行操作系统程序// perror("execl");// 测试,不会执行子进程的剩下逻辑printf("i am child process, pid : %d\n", getpid());}// 测试,只执行父进程程序for(int i = 0; i < 3; i++) {printf("i = %d, pid = %d\n", i, getpid());}return 0;
}
hello可执行文件内容#include int main() { printf("hello, world\n");return 0;
}

execlpint execlp(const char *file, const char *arg, ... /* (char *) NULL */);
/* #include int execlp(const char *file, const char *arg, ... );- 会到环境变量中查找指定的可执行文件,如果找到了就执行,- 找不到就执行不成功。- 参数:- file:需要执行的可执行文件的文件名a.outps- arg:是执行可执行文件所需要的参数列表第一个参数一般没有什么作用,为了方便,一般写的是执行的程序的名称从第二个参数开始往后,就是程序执行所需要的的参数列表。参数最后需要以NULL结束(哨兵)- 返回值:只有当调用失败,才会有返回值,返回-1,并且设置errno如果调用成功,没有返回值。int execv(const char *path, char *const argv[]);argv是需要的参数的一个字符串数组char * argv[] = {"ps", "aux", NULL};execv("/bin/ps", argv);int execve(const char *filename, char *const argv[], char *const envp[]);char * envp[] = {"/home/nowcoder", "/home/bbb", "/home/aaa"};*/
#include
#include int main() {// 创建一个子进程,在子进程中执行exec函数族中的函数pid_t pid = fork();if(pid > 0) {// 父进程printf("i am parent process, pid : %d\n",getpid());sleep(1);}else if(pid == 0) {// 子进程execlp("ps", "ps", "aux", NULL);printf("i am child process, pid : %d\n", getpid());}for(int i = 0; i < 3; i++) {printf("i = %d, pid = %d\n", i, getpid());}return 0;
}
execle int execle(const char *path, const char *arg, .../*, (char *) NULL, char *
const envp[] */);
execv int execv(const char *path, char *const argv[]);- argv是需要的参数的一个字符串数组- char * argv[] = {"ps", "aux", NULL};- execv("/bin/ps", argv);
execvp int execvp(const char *file, char *const argv[]);
execvpe int execvpe(const char *file, char *const argv[], char *const envp[]);
execve -linux函数 int execve(const char *filename, char *const argv[], char *const envp[]);

/*#include void exit(int status);#include void _exit(int status);status参数:是进程退出时的一个状态信息。父进程回收子进程资源的时候可以获取到。
*/
#include
#include
#include int main() {printf("hello\n"); // 自动刷新缓存区printf("world"); // C库的函数,会先放到缓冲区// exit(0); // 标准c库,与return 0 功能一样_exit(0); // linux库,没有缓冲区,最终程序只打印已经刷新缓冲区的helloreturn 0;
}
wait 函数/*#include #include pid_t wait(int *wstatus);功能:等待任意一个子进程结束,如果任意一个子进程结束了,此函数会回收子进程的资源。参数:int *wstatus进程退出时的状态信息,传入的是一个int类型的地址,传出参数。返回值:- 成功:返回被回收的子进程的id- 失败:-1 (所有的子进程都结束,调用函数失败)调用wait函数的进程会被挂起(阻塞),直到它的一个子进程退出或者收到一个不能被忽略的信号时才被唤醒(相当于继续往下执行)如果没有子进程了,函数立刻返回,返回-1;如果子进程都已经结束了,也会立即返回,返回-1.*/
#include
#include
#include
#include
#include int main() {// 有一个父进程,创建5个子进程(兄弟)pid_t pid;// 创建5个子进程for(int i = 0; i < 5; i++) {pid = fork();if(pid == 0) { // 防止子进程递归fockbreak;}}if(pid > 0) {// 父进程while(1) {printf("parent, pid = %d\n", getpid());// 子进程不退出,父进程就一直阻塞不进行,直到一个子进程退出int ret = wait(NULL); // 不需要获得子进程退出的状态if(ret == -1) {break;}// 打印子进程是否被释放的信息printf("child die, pid = %d\n", ret);sleep(1);}} else if (pid == 0){// 子进程while(1) {printf("child, pid = %d\n",getpid()); sleep(1); }}return 0; // exit(0)
}

#include
#include
#include
#include
#include int main() {// 有一个父进程,创建5个子进程(兄弟)pid_t pid;// 创建5个子进程for(int i = 0; i < 5; i++) {pid = fork();if(pid == 0) { // 防止子进程递归fockbreak;}}if(pid > 0) {// 父进程while(1) {printf("parent, pid = %d\n", getpid());// 子进程不退出,父进程就一直阻塞不进行,直到一个子进程退出 int st;int ret = wait(&st); // 获取子进程退出的状态if(ret == -1) {break;}// 获取子进程退出的状态信息,与宏比较if(WIFEXITED(st)) {// 是不是正常退出printf("退出的状态码:%d\n", WEXITSTATUS(st));}if(WIFSIGNALED(st)) {// 是不是异常终止printf("被哪个信号干掉了:%d\n", WTERMSIG(st));}// 打印子进程是否被释放的信息printf("child die, pid = %d\n", ret);sleep(1);}} else if (pid == 0){// 子进程printf("child, pid = %d\n",getpid()); sleep(1); exit(0); // 正常退出}return 0; // exit(0)
}
waitpid 函数/*#include #include pid_t waitpid(pid_t pid, int *wstatus, int options);功能:回收指定进程号的子进程,可以设置是否阻塞。参数:- pid:pid > 0 : 某个子进程的pidpid = 0 : 回收当前进程组的所有子进程 pid = -1 : 回收所有的子进程,相当于 wait() (最常用)pid < -1 : 某个进程组的组id的绝对值,回收指定进程组中的子进程- options:设置阻塞或者非阻塞0 : 阻塞WNOHANG : 非阻塞- 返回值:> 0 : 返回子进程的id= 0 : options=WNOHANG, 表示还有子进程或者= -1 :错误,或者没有子进程了
*/
#include
#include
#include
#include
#include int main() {// 有一个父进程,创建5个子进程(兄弟)pid_t pid;// 创建5个子进程for(int i = 0; i < 5; i++) {pid = fork();if(pid == 0) {break;}}if(pid > 0) {// 父进程while(1) {printf("parent, pid = %d\n", getpid());sleep(1);int st;// int ret = waitpid(-1, &st, 0); // 阻塞int ret = waitpid(-1, &st, WNOHANG); // 非阻塞if(ret == -1) {break;} else if(ret == 0) {// 说明还有子进程存在continue;} else if(ret > 0) { // 回收到了子进程if(WIFEXITED(st)) {// 是不是正常退出printf("退出的状态码:%d\n", WEXITSTATUS(st));}if(WIFSIGNALED(st)) {// 是不是异常终止printf("被哪个信号干掉了:%d\n", WTERMSIG(st));}printf("child die, pid = %d\n", ret);}}} else if (pid == 0){// 子进程while(1) {printf("child, pid = %d\n",getpid()); sleep(1); }exit(0);}return 0;
}




