马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
目次
1.进程创建
1.1字符串常量为什么不可以修改?
1.2代码段和数据段到底是什么?
1.3.fork函数初识
1.4.fork函数返回值
1.5.写时拷贝:
1.6写时拷贝按需举行的原理(与页表的权限有关)
1.7.fork常规用法
2.进程终止
2.1.进程退进场景
2.2.进程常见退出方法
2.3.exit()函数和_exit()函数辨析
2.4.辨析退出码、错误码、退出信号
2.4.1退出码转换为错误码的操作
2.5.平凡函数的返回值
3.进程等待
3.1.进程等待须要性
3.2wait()和waitpid()函数
wait
功能:
waitpid
功能:
返回值:
3.3阻塞等待和非阻塞等待
3.3.1、阻塞等待
3.3.2非阻塞等待
3.4通过位操作获取子进程的退出码和退出信号
4. 进程程序替换
4.1. 概念与原理
概念:
原理:
4.2. exec*系列替换函数
函数表明
命名理解
4.3那我们详细怎样举行进程替换呢?
4.4替换为什么没有影响父进程?
1.进程创建
1.1字符串常量为什么不可以修改?
这里为什么编译不通过?
因为字符串具有常量属性,字符常量不可被修改。这里的问题是字符串为什么会有常量属性呢?
这里是字符串常量,具有常性,所以存储在了代码段当中的常量区。
因为这里的字符串地址一定是虚拟地址,而改成字符H,是在物理空间上做修改,所以此时就需要页表举行映射,而这里就会有权限的限定,只读的权限,不可修改,所以才会有字符常量不可修改这样的限定,本质是操作系统的锅!
1.2代码段和数据段到底是什么?
代码段里面存储的是可执行代码和常量区;数据段存储的是全局变量和静态变量
1.3.fork函数初识
在linux中fork函数时非常告急的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。
#include <unistd.h>
pid_t fork(void);
返回值:自进程中返回0,父进程返回子进程id,出错返回-1
1.4.fork函数返回值
子进程返回0,
父进程返回的是子进程的pid
1.5.写时拷贝:
为什么要用拷贝的形式,父进程直接将资源给子进程不就行了吗?
我们通常的操作有增删改查,可能会直接修改了原来的内容,所以需要额外拷贝一份资源。
通常,父子代码共享,父子再不写入时,数据也是共享的,当恣意一方试图写入,便以写时拷贝的方式拷贝副本。
1.6写时拷贝按需举行的原理(与页表的权限有关)
在举行拷贝的时候,会将数据段的页表权限改成只读权限!然后任何一方想要举行写入的时候,这个时候操作系统就会介入,将权限改返来可读可写,所以当我们的子进程举行写入的时候就会报错缺页停止。操作系统就会介入,这样就写时拷贝就可以按需举行!
页表不仅仅有将虚拟地址转换为物理内存,还会有权限位
1.7.fork常规用法
- 一个父进程希望复制自己,使父子进程同时执行不同的代码段。比方,父进程等待客户端请求,生成子进程来处理请求。
- 一个进程要执行一个不同的程序。比方子进程从fork返回后,调用exec函数
2.进程终止
2.1.进程退进场景
- 进程代码运行完毕,效果正确
- 进程代码运行完毕,效果不正确
- 进程代码没用执行完,进程出异常了
2.2.进程常见退出方法
正常终止(可以通过 echo $? 查看进程退出码):
1. 从main返回
2. 调用exit
3. _exit
异常退出:
ctrl + c,信号终止
2.3.exit()函数和_exit()函数辨析
exit函数会支持革新缓冲区,_exit函数不支持。exit()底层封装了_exit(),两者是上下层关系
2.4.辨析退出码、错误码、退出信号
退出码包罗错误码,当退出码是0的时候,表示程序正常退出;假如退出码!=0,这个退出码就表示错误码。然后利用sterror函数将其举行转换。
进程假如在执行的时候异常了,os会发送信号终止它,这个就是退出信号。非0就代表程序出异常,0代表程序正常执行。
任何进程终极的执行情况,我们可以使用两个数字表明详细的执行情况,一个是退出码,另一个就是退出信号
2.4.1退出码转换为错误码的操作
使用语言或者系统自带的方法举行转化,比方:在linux中,使用strerror()函数。
char* strerror(int errnum);
- #include<stdio.h>
- #include<string.h>
- int main()
- {
- for(int i = 0; i < 100; i++)
- printf("%d:%s\n", i, strerror(i));
-
- return 0;
- }
复制代码
2.5.平凡函数的返回值
- 平凡函数退出,仅仅表示函数调用完毕。
- 函数也被称为子程序,与进程退出时返回退出码类似,函数执行完毕也会返回一个值,这个值通常用于表示函数的执行效果或状态。
- 调用函数,我们通常想看到两种效果:a.函数的执行效果(函数的返回值);b.函数的执行情况(函数是否乐成执行了预期的任务),比方:fopen()函数的执行情况是通过其执行效果来间接表示。
fopen函数举例:返回了非空的FILE*指针,则可以为函数执行乐成;返回了NULL,则可以为函数执行失败,需要进一步查抄错误的缘故原由(errno变量或调用perror()函数)。
3.进程等待
3.1.进程等待须要性
- 子进程退出,父进程假如不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
- 进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
- 父进程派给子进程的任务完成的怎样,我们需要知道。如子进程运行完成,效果对还是不对,或者是否正常退出。
- 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息
3.2wait()和waitpid()函数
wait
pid_t wait(int* status);
功能:
等待恣意一个子进程结束,并回收其资源。
返回值:调用乐成,返回已经结束进程的PID,同时获取到了子进程的退出状态码;调用失败,返回-1,并设置错误码以指示错误的缘故原由。
参数status:输出型参数,用于存储子进程的退出状态,由OS填充,假如不需要这个信息,可以转达NULL,否则,OS会根据该参数,将子进程的信息反馈给父进程。
waitpid
pid_t waitpid(pid_t pid, int* status, int options);
功能:
等待恣意一个子进程或者指定的子进程结束,并回收其资源。
参数pid:假如pid = -1,等待恣意一个子进程,与wait等效;假如pid > 0,等待其进程的PID与pid相等的子进程。
参数option:假如option = 0,则为阻塞等待;假如option = WNOHANG,则为非阻塞等待。
返回值:
调用乐成,返回收集到的子进程的PID,同时获取到了子进程的退出状态码;调用失败,返回-1,并设置错误码以指示错误的缘故原由;假如为非阻塞等待,waitpid调用乐成且没有收集到已结束的子进程,则返回0。
3.3阻塞等待和非阻塞等待
3.3.1、阻塞等待
界说:进程在发出某个请求(如:I/O操作、等待某个条件成立等)后,假如请求不能立即得到满意(如:数据未准备好、资源被占用等),进程会被挂起,在此期间无法继承执行其他任务,直到等待条件满意或被叫醒。
一心一意,专心做一件事!
特点:
a.行为 -> 进程在等待期间无法执行其他任务。
b.触发方式 -> 等待由外部条件触发(如:数据到达、资源释放等)。
c.管理层面:由操作系统或者底层系统资源管理。
d.服从与并发性:服从低。
应用场景:实时性要求不高,等待时间相对比较短的情况,如:简朴文件的读写操作。
- #include<stdio.h>
- #include<stdlib.h>
- #include<unistd.h>
- #include<sys/types.h>
- #include<sys/wait.h>
- int main()
- {
- pid_t id = fork();
-
- if(id == 0) //子进程
- {
- int cnt = 5;
- while(cnt)
- {
- printf("child is running, id:%d, ppid:%d\n", getpid(), getppid());
- sleep(1);
- cnt--;
- }
- exit(1); //子进程退出
- }
-
- int status = 0; //存储子进程退出状态
- pid_t rid = waitpid(id, &status, 0); //父进程等待 —— 阻塞等待
- if(rid > 0) //等待成功
- printf("wait success, status:%d\n", status);
- else if(rid == -1) //调用失败
- perror("wait error!\n");
-
- return 0;
- }
复制代码 3.3.2非阻塞等待
界说:进程在发出某个请求后,不会被立即挂起已等待请求的完成,即使请求不能立即得到满意,进程在等待期间可以继承执行其他任务,同时可能会以某种方式(轮询访问、回调等)定期查抄请求状态或者等待效果的通知。
特点:
a.行为 -> 进程在等待期间可以执行其他任务;
b.触发方式 -> 可能通过编程的方式实现,如:轮询、回调等。
c.管理层面:在应用层通过编程实现。
d.服从与并发性:服从高,提高并发性和响应能力。
应用场景:需要高并发和响应能力的场景,如:在网络编程中,服务器同时处理多个客户端的请求。
- #include<stdio.h>
- #include<stdlib.h>
- #include<unistd.h>
- #include<sys/types.h>
- #include<sys/wait.h>
-
- #define SIZE 5
- int main()
- {
- Inittask();
- pid_t id = fork();
- if(id == 0) //子进程
- {
- int cnt = 2;
- while(cnt)
- {
- printf("I am a process, id:%d, ppid:%d\n", getpid(), getppid()); sleep(1);
- cnt--;
- }
- exit(1); //子进程退出
- }
-
- int status = 0; //存储子进程退出状态
- while(1) //基于非阻塞轮询的访问
- {
- pid_t rid = waitpid(id, &status, WNOHANG); //非阻塞等待
- if(rid > 0) //调用成功,收集到了已经结束的子进程 {
- printf("wait success, status:%d\n", status);
- break;
- }
- else if(rid == 0) //调用成功,未收集到已经结束的子进程
- {
- printf("child is running, father do other thing!\n");
- printf("------------ Task begin ----------------\n");
- executeTask(); //等待期间,执行其他任务
- printf("------------ Task end ----------------\n");
- }
- else //调用失败
- {
- perror("wait error\n");
- break;
- }
-
- sleep(1);
- }
- return 0;
- }
复制代码 3.4通过位操作获取子进程的退出码和退出信号
我们这里只解说16位的情况下
- #include<stdio.h>
- #include<stdlib.h>
- #include<unistd.h>
- #include<sys/types.h>
- #include<sys/wait.h>
- int main()
- {
- pid_t id = fork();
-
- if(id == 0) //子进程
- {
- int cnt = 5;
- while(cnt)
- {
- printf("child is running, id:%d, ppid:%d\n", getpid(), getppid());
- sleep(1);
- cnt--;
- }
- exit(1); //子进程退出
- }
-
- int status = 0; //存储子进程退出状态
- pid_t rid = waitpid(id, &status, 0);
- if(rid > 0) //等待成功
- printf("wait success, status:%d, exit code:%d, exit sign:%d\n",
- status, (status>>8)&0xff, status&0x7f); //位操作获取子进程的退出码、退出信号
-
- return 0;
- }
复制代码 4. 进程程序替换
4.1. 概念与原理
概念:
它答应一个进程在执行期间,用一个新的程序来替换当前正常执行的程序,即:用全新的程序替换原有的程序。
这意味着进程在调用一种exec函数,当前进程的用户空间代码和数据被新程序的代码和数据完全替换(覆盖),重新程序的启动例程开始执行。
留意:调用exec函数,并不会创建新的进程,而是对原有进程的资源举行替换,因此调用exec前后该进程的pid并未发生改变。
原理:
加载新程序 -> 替换当前程序 -> 更新页表 -> 执行新程序。
- 加载新程序:当进程决定举行程序替换时(调用exec函数),它会请求OS将全新程序(代码和数据)从磁盘中加载到内存。
- 更新页表:为了实现替换,OS需要更新页表,将原来指向旧程序代码的虚拟地址映射到新程序代码的物理地址上,这样,就会执行新程序的代码。
4.2. exec*系列替换函数
有六种以exec开头的函数,统称exec函数:
#include <unistd.h>`
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
函数表明
这些函数假如调用乐成则加载新的程序从启动代码开始执行,不再返回。
假如调用出错则返回-1
所以exec函数只有出错的返回值而没有乐成的返回值。
命名理解
这些函数原型看起来很轻易混,但只要掌握了规律就很好记。
l(list) : 表示参数采用列表
v(vector) : 参数用数组
p(path) : 有p主动搜索环境变量PATH
e(env) : 表示自己维护环境变量
究竟上,只有execve是真正的系统调用,其它五个函数终极都调用 execve,所以execve在man手册 第2节,其它函数在man手册第3节。这些函数之间的关系如下图所示
4.3那我们详细怎样举行进程替换呢?
我们要知道当我们把一个程序举行./,那么这个程序就变成了一个进程,而在我们的这个进程中执行了关于进程替换的函数,那么该进程就会被替换,执行另一个进程!
我们不一定要让一个进程直接举行替换,可以创建子进程,让子进程举行替换,让父进程等待我们的效果就可以.
4.4替换为什么没有影响父进程?
因为进程具有独立性,我们将子进程举行替换,发生写时拷贝,不会影响父进程
一次想生成两个可执行文件,就需要这么写,不然makefile默认值生成第一条指令!
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |