1.进程创建
简单讲一下进程创建主要会用到fork()函数来创建进程;
pid_t fork(void); 头文件:<unistd.h>
特点:父子进程代码共享,数据独有,用到了写时拷贝技术。
返回值:子进程中返回0,父进程返回子进程的pid,出错则返回-1
此外**vfork()**函数也可以创建进程, pid_t vfork(void) ;
创建子进程,并且阻塞父进程;父子进程共用同一块地址空间;
不熟悉进程创建的可以参考这里
2.进程退出
进程退出共有三种方式:
1.main()函数中使用return;
2.使用库函数exit();
3.系统调用:_exit();
注意1和2两种退出方式会再退出前刷新缓冲区,而_exit()会直接释放掉资源;
可通过perrror(const char *),头文件<stdio.h>,使用时输入空字符串即可; 还有函数strerror(errno),头文件<string.h>,打印出程序错误的原因;其中errno为错误编号。
运行这一小段代码可以查看系统中的错误码0-255都代码表了什么含义,还使用到**perror()**函数打印错误原因;
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main ()
{
int i;
for (i=0 ;i<266;i++){
printf("%s\n",strerror(i));
}
pid_t pid = fork();
if(pid <0){
perror("fork error");
return -1;
}
char *p =NULL;
memcpy(p,"aabbcc",3);
perror();
return 0 ;
}

3.进程等待
父进程等待子进程退出,获取子进程的推出返回值,释放子进程的资源,避免其成为僵尸进程。
使用到的函数:
1.pid_t wait(int * status );能够处理任意一个子进程退出;
这个函数接口是一个阻塞函数,如果子进程没有退出会一直等待;
status 为获取到子进程退出的返回值,成功则返回PID,失败 -1(没有子进程);
2.pid_t waitppid(pid_t pid, int*status,int options);能够等待任意一个子进程退出,或等待指定子进程(pid)的退出。
参数: pid 为-1的时候 接受任意一个子进程退出,大于0的时候为指定子进程的pid时则等待指定子进程退出;
status 用于获取子进程退出的返回值
options 0默认为阻塞状态,若将其设置为WNOHANG则为非阻塞状态,这样就比较友好一点,父进程可以边等待子进程的退出,一边执行自己的任务。
如果子进程已退出,则不需要等待。
阻塞与非阻塞:
阻塞:为了完成一个任务,发起调用,若当前不具备完成条件,则一直等待;
非阻塞:为了完成一个任务,发起调用,若当前不具备完成条件,则报错返回。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
pid_t pid = fork();
if (pid < 0) {
perror("fork error");//程序的一种失败错误处理
return -1;
}else if (pid == 0) {
sleep(5);
exit(99);//退出子进程
}
int ret, status;
//ret = wait(&status); //使用wait()等待子进程退出,阻塞;
// if (ret > 0) {
// if (WIFEXITED(status)) {
// printf("%d\n", WEXITSTATUS(status));
// } else {
// printf("进程异常退出\n");
// }
// }
while((ret = waitpid(-1, &status, WNOHANG)) == 0) { //使用waitpid()等待子进程退出,非阻塞;
printf("没有子进程退出,打一把麻将\n");
sleep(1);
}
while(1) {
printf("------------\n");
sleep(1);
}
return 0;
}

关于上面status返回值还需要做进一步说明,status保存在一个四字节的空间里,如图:
倒数第最后一个字节后七位保存了异常信号值,异常信号值若为0,则表示进程正常退出,若不为0则表示进程异常退出,在进程异常退出的情况下,返回值是不具有意义的。可使用status & 0x7f 获异常信号也可使用函数**WIFEXITED(status)判断是否异常返回。
第三个字节保存了函数的推出返回值,(status>>8)0xff 可获取其推出返回值,或使用函数WEXITSTATUS(status)**获取。

4.进程替换
替换一个正在调度的程序. 子进程通过调用 exec 系列函数时, 加载一个新的程序到内存上,更新当前进程的页表隐映射信息,让当前进程映射到新的页表信息上去。本质上并没有创建新的进程, 进程的 ID 也没有发生变化。

进程替换会使用到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[]);
int execve(const char *path, char *const argv[], char *const envp[]);
第一个参数:新的程序文件路径名;
第二个参数:程序的运行参数 -l -o 等;
第三个参数:程序的环境变量;
关于这几个函数的区别和简单使用:
1.有没有p的区别:在于程序文件是否需要带路径;有P时可以不带路径,但是程序必须要在PATH环境变量指定的路径下,这样才能找到要运行的程序;
举几个例子:
1.int execl(const char *path, const char *arg, …);与int execlp(const char *path, const char *arg, …, char *const envp[]);
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main (int argc, char *argv[])
{
printf("XXXXX-----XXXXX\n");
execl("/usr/bin/ls","ls","-a","-l",NULL);///usr/bin/ls为ls程序路径,可使用whereis ls 命令查看其路径
//execlp("ls","ls","-a","-l",NULL)//可以不用给程序的路径但程序要在PATH环境变量指定的路径下 ,运行结果与上面语句运行结果相同。
printf("DDDDD----DDDDD\n");
return 0;
}

2.有没有e的区别:在于程序是否自己设定环境变量,有E就是自己设定(覆盖式),没有e则默认使用自己的。
举个例子:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main (int argc, char *argv[])
{
printf("XXXXX-----XXXXX\n");
char *env[] = {"MYVAL=1000", NULL}; //这里设置了一个环境变量,注意为覆盖式赋值
//execlp("./arg", "arg", "-a", "-l", NULL);
execle("./arg", "arg", "-a", "-l", NULL, env); //程序替换为arg程序
printf("DDDDD----DDDDD\n");
return 0;
}
arg.c代码,可查看函数的命令参数和环境变量
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
//argc 程序运行参数的个数
//argv 所有的运行参数的字符串的地址的保存位置
int main (int argc, char *argv[], char *env[])
{
int i;
for (i = 0; argv[i] != NULL; i++) {
printf("argv[%d]=[%s]\n", i, argv[i]);
}
for (i = 0; env[i] != NULL; i++) {
printf("env[%d]=[%s]\n", i, env[i]);
}
return 0;
}

3.l和v的区别:在于程序运行参数的赋予方式不同,v则表示参数要用列表形式传递,注意第0个参数为程序自身,最后一个参数应为NULL。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main (int argc, char *argv[])
{
printf("XXXXX-----XXXXX\n");
char *env[] = {"MYVAL=1000", NULL}; //这里设置了一个环境变量,注意为覆盖式赋值
char *arg[] = {"./arg", "-a", "-l", NULL};
execve("./arg", arg, env); //使用数组来传递运行参数
printf("DDDDD----DDDDD\n");
return 0;
}

4.如图:最后一个函数为系统调用接口,其它五个函数均对其功能做了封装,适应某种特定场景的使用,为库函数。

本文详细介绍了进程管理中的关键概念和技术,包括进程创建、进程退出、进程等待及进程替换等内容。通过具体的函数介绍和示例代码,帮助读者理解这些概念并掌握实际操作。
911

被折叠的 条评论
为什么被折叠?



