一.进程
1.概念复习
程序:程序(program)是存放在磁盘文件中的可执行文件
进程:程序的执行实例被称为进程(process)。
- 进程具有独立的权限与职责。
- 如果系统中某个进程崩溃,它不会影响到其余的进程。
- 每个进程运行在其各自的虚拟地址空间中,进程之间可以通过由内核控制的机制相互通讯。
进程ID:每个linux
进程都一定有一个唯一的数字标识符,称为进程ID(process ID
),进程ID总是一非负整数。
task_struct
===》进程表项(进程控制块)
2.main()函数
我们知道:
void main(){}int main()
{return 0;
}
但是main()
函数我们或多或少见识过带参数的,比如:
int main(int argc,char *argv[])
{return 0;
}
argc
:函数内参数的个数,编译器自动计算传入argv
:函数内的参数都会被当作字符串类型存储到这里,中间默认用空格分隔
启动例程:
- 启动例程在
main()
函数执行之前内核就会启动- 在编译时,启动例程代码就会和用户写的代码进行编译链接到可执行文件中
- 启动例程的作用是收集命令行的参数传递给
main()
函数的argc
和argv
3.进程终止方式
正常终止:
- 从
main
函数返回 - 调用
exit
(标准c库函数) - 调用
_exit
或_Exit
(系统调用) - 最后一个线程从其启动例程返回
- 最后一个线程调用
pthread_exit
异常终止:
- 调用
abort
- 接受到一个信号并终止
- 最后一个线程对取消请求做处理响应
4.atexit函数
#include <stdlib.h>
int atexit(void (*function)(void)) //传入自定义终止函数的函数指针//返回: 若成功则为0,若出铅则为-1
//功能: 向内核登记终止函数
- 每个启动的进程都默认登记了一个标准的终止函数
- 终止函数在进程终止时释放进程所占用的一些资源
- 登记的多个终止函数执行顺序是以栈的方式执行,先登记的后执行。
二.进程控制
1.fork()函数
作用:创建一个子进程
通过fork()
创建的子进程类似于父进程的复制版本,fork()
以前的代码子进程虽然也有但已不再执行,而fork()
后面的代码父子进程分开执行,从而导致有两个返回值,子进程创建成功则返回0,而父进程则返回创建的子进程的pid
。
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>void term_fun1()
{printf("first term function\n");
}void term_fun2()
{printf("second term function\n");
}int main()
{printf("before fork1\n");printf("before fork2\n");pid_t pid=fork();if(pid<0){perror("fork error\n");exit(1);}else if(pid==0){printf("---child process is created\n");}else if(pid>0){printf("---praent process:my child process is %d\n",pid);}printf("===============================end process\n");
}
结果:
before fork1
before fork2
—praent process:my child process is 10114
===============================end process
—child process is created
===============================end process
先执行父进程,再执行子进程。
2.getpid与getppid
getpid
:获取自己的pid号getppid
:获取父进程的pid号
改一下上面代码:
if(pid<0){perror("fork error\n");exit(1);}else if(pid==0){printf("---child process is created,my_pid:%d,my_prapid:%d\n",getpid(),getppid());}else if(pid>0){printf("---praent process:my child process is %d,my_pid:%d,my_prapid:%d\n",pid,getpid(),getppid());}
3.循环建立n个进程
问题一:
- 怎么建立n个?
- 怎么保证顺序?
int main()
{for(int i=0;i<5;i++){if(fork()==0)break;}if(5==i){sleep(5);printf("I'm parent\n");}else{sleep(i);printf("I'm %dth child\n",i+1);}return 0;
}
三.其他函数
1.exec函数族
fork
创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec
函数以执行另一个程序。当进程调用一种exec
函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用 exec
并不创建新进程,所以调用exec
前后该进程的 id 并未改变。重点掌握:execl
和execlp
—execlp
-
加载一个进程,借助 PATH 环境变量
int execlp(const char *file, const char *arg, ...);
-
参数 1:要加载的程序的名字。该函数需要配合 PATH 环境变量来使用,当 PATH 中所有目录搜索后没有参数 1(即环境变量中没有对应的程序)则出错返回。
-
该函数通常用来调用系统程序。如:
ls、date、cp、cat
等命令。“
—execl
-
加载一个进程,通过 路径+程序名 来加载。
int execl(const char *path, const char *arg, ...);
-
对比
execlp
,如加载"ls"命令带有-1,-F 参数,使用参数1给出的绝对路径搜索。execlp("ls","ls","-l","-F",NULL); execl("/bin/s","ls","-l","-F",NULL);
2.孤儿进程与僵尸进程
-
孤儿进程:父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程变为
init
进程,也可以叫init
进程领养孤儿进程。怎么处理一个孤儿进程:直接
kill
杀死 -
僵尸进程:进程终止,父进程尚未回收,子进程残留资源PCB存放在内核中,变成僵尸进程。
怎么处理一个僵尸进程:
kill
杀死其父进程,使其变为孤儿进程,init
进程发现其是僵尸进程后自动被回收。 -
守护进程:守护进程运行在后台,不跟任何控制终端关联
作用:确保运行程序完整执行
3.wait和waitpid
作用:waitpid
同wait
,但可以指定pid
进程清理
注意:一次wait/waitpid
的函数调用,只能回收一个子进程。
语法:
pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);
wait
函数成功执行返回要终止(回收)子进程的pid
;失败则返回-1wait
和waitpid
里的int *wstatus
参数意思是把回收子进程的状态信息存储到int *
所指的内存空间里,这里的状态信息是以宏的方式存储options
:利用进程发送变换的状态进行回收,一般选择WNOHANG
waitpid
函数的pid
传-1,代表回收任意子进程
代码理解:
wait(NULL); //阻塞直至回收任意一个进程
wpid=waitpid(-1,NULL,WNOHANG); //回收任意子进程,没有结束的子进程,父进程直接返回0
wpid=waitpid(-1,NULL,0); //阻塞回收某一子进程
四.进程间通信(未完待续)
同一主机的进程间通信:
- 管道(Pipe):用于父子进程间通信
- 命名管道(FIFO):不相关的进程间通信,不适合大量数据传输
- 消息队列(Message Queue):适用于需要缓冲和异步处理的场景
- 共享内存(Shared Memory):允许多个进程访问同一块内存区域,适合大数据量传输
- 信号量(Semaphore):用于控制对共享资源的访问
- 信号(Signal):用于通知接收进程某个事件已经发生(重点)
不同主机间的进程通信:
- 服务器与客户端(Socket):网络编程