做算命网站挣钱吗,新浪云能用wordpress,怎么做服装网站,qq免费搭建网站C语言进程的相关操作
进程简介
每个进程都有一个非负整数形式到的唯一编号#xff0c;即PID#xff08;Process Identification#xff0c;进程标识#xff09;PID在任何时刻都是唯一的#xff0c;但是可以重用#xff0c;当进程终止并被回收以后#xff0c;其PID就可…C语言进程的相关操作
进程简介
每个进程都有一个非负整数形式到的唯一编号即PIDProcess Identification进程标识PID在任何时刻都是唯一的但是可以重用当进程终止并被回收以后其PID就可以为其他进程使用进程的PID由系统内核根据延迟重用算法生成以确保新进程的PID不同于最近终止进程到的PID其中0号进程叫做交换进程系统内核中的一部分所有进程的根进程磁盘上没有它的可执行文件1号进程是init进程在系统自举过程结束时由调度进程创建读写与系统相关的初始化文件引导系统至一个特定状态以超级用户特权运行的普通进程永不终止除去调度进程以外系统中的每个进程都有一个唯一的父进程对任何一个子进程而言其父进程的PID即是它的PPID下面这些函数都包含在unistd.h头文件中pid_t getpid(void);返回调用进程的PIDpid_t getppid(void);返回调用进程的父进程的PIDuid_t getuid(void);返回调用进程的实际用户IDgid_t getgid(void);返回调用进程的实际组IDuid_t geteuid(void);返回调用进程的有效用户IDgid_t getegid(void);返回调用进程的有效组ID
创建子进程 创建子进程的函数包含在unistd.h头文件中 fork函数 pid_t fork(void); 功能创建调用进程的子进程返回值失败返回-1成功情况下返回的变量在父进程中是PID在子进程中是0可以通过这个返回值来执行父进程和子进程当系统中的总的线程数达到了上限或者用户的总进程达到了上限fork函数会失败。 创建子进程示例代码 #include stdio.h
#include unistd.hint main(void)
{printf(haha\n);// 创建子进程int pid fork();printf(heihei\n);return 0;
}/*
haha
heihei
heihei
*/父子进程间的关系 以下是父子进程中数据相关copy的示例图 验证上图 #include stdio.h
#include stdlib.h
#include unistd.h
#include time.hint global 100; // 父进程全局变量-数据区int main(void)
{
int local 200; // 父进程局部变量-栈区
int *heap malloc(sizeof(int)); // 动态分配内存-堆区
*heap 3;printf(父进程第一次打印: PID-%d %p-%d %p-%d %p-%d\n, getpid(), global, global, local, local, heap, *heap);
// 创建子进程
pid_t pid fork();
if(pid 0)
{
// 子进程操作数据会从父进程copy一份过来这里执行操作
printf(子进程打印: PID-%d PPID-%d %p-%d %p-%d %p-%d\n, getpid(), getppid(), global, global, local, local, heap, *heap);
return 0;
}
sleep(1); // 这里等1s让子进程
printf(父进程第二次打印: PID-%d %p-%d %p-%d %p-%d\n, getpid(), global, global, local, local, heap, *heap);return 0;
}/*
父进程第一次打印: PID-1674604 0x5577e1acc010-100 0x7ffd4d4bfaa8-200 0x5577e23422a0-3
子进程打印: PID-1674605 PPID-1674604 0x5577e1acc010-101 0x7ffd4d4bfaa8-201 0x5577e23422a0-4
父进程第二次打印: PID-1674604 0x5577e1acc010-100 0x7ffd4d4bfaa8-200 0x5577e23422a0-3这里的父进程和子进程地址一样是虚拟地址里面一样因为每个进程都有一个独立的虚拟地址池相互不影响的
发现子进程跟父进程互相不影响验证了上图的案例
*/父子进程操作文件其实是共享一个文件表项的 验证上图 #include stdio.h
#include string.h
#include fcntl.h
#include unistd.h
#include time.hint main(void)
{// 父进程打开文件int fd open(./test.txt, O_WRONLY | O_CREAT | O_TRUNC, 0664);if(fd -1){perror(open);return -1;}// 父进程写入数据char *data hello bhlu!;if(write(fd, data, strlen(data)) -1){perror(write);return -1;}// 创建子进程pid_t pid fork();if(pid 0){// 子进程修改文件读写位置if(lseek(fd, -5, SEEK_END) -1){perror(lseek);return -1;}return 0;}// 再次插入数据验证子进程修改的读写位置是否生效sleep(1); // 先等1s让子进程执行完data linux\n;if(write(fd, data, strlen(data)) -1){perror(write);return -1;}// 关闭文件close(fd);return 0;
}/*
cat test.txt
hello linux
发现是修改成功的说明上图是对的子进程和父进程共用一个文件表项
*/进程的终止
以下内容只是简单的介绍进程的终止以便理解 进程的终止分为两种 正常终止分为三种情况 main函数中正常返回 使用exit函数终止exit函数可以在任何函数中执行令进程结束return语句只有在main函数中执行才能令进程结束 #include stdlib.hvoid exit(int status);
/*
功能: 令进程终止
参数: status 进程的退出码相当于main函数的返回值
无返回值
*//*
exit函数在终止前会做以下几件收尾工作
1. 调用实现通过atexit或on_exit函数注册的函数退出函数
2. 冲刷并关闭所有仍处于打开状态的标准I/O流
3. 删除所有通过tmpfile函数创建的临时文件
4. 执行_exit(status);
使用exit函数令进程终止通常使用EXIT_SUCCESS和EXIT_FAILUR两个宏
EXIT_SUCCESS - 1; EXIT_FAILUR - 0;
*/调用_exit/_Exit函数令进程终止 // _exit函数
#include unistd.hvoid _exit(int status);
/*
参数: status 进程的退出码相当于main函数的返回值
无返回值
*/// _Exit函数
#include stdlib.hvoid _Exit(int status);
/*
参数: status 进程的退出码相当于main函数的返回值
无返回值
*//*
_exit函数在终止前会做以下几件收尾工作
1. 关闭所有仍处于打开状态的文件描述符
2. 将调用进程的所有子进程托付过init进程
3. 向调用进程的父进程发送SIGCHLD(7)信号
4. 令调用进程终止运行将status的低八位作为退出码保存在其终止状态中
*/异常终止 进程执行了系统认为具有危险性的操作时或者系统本身发生故障或意外内核会向进程发送特定的信号 SIGILL(4) - 进程试图执行非法指令
SIGBUS(7) - 硬件或对齐错误
SIGEPE(8) - 浮点异常
SIGSEGV(11) - 无效内存访问
SIGPWR(30) - 系统供电不足人为触发信号 SIGINT(2) - Ctrlc
SIGQUIT(3) - Ctrl\
SIGKILL(9) - 不能被捕获或忽略的进程终止信号
SIGTERM(15) - 可以被捕获或忽略的进程终止编号向进程自己发送信号 #include stdlib.hvoid abort(void);
/*
功能: 想进城发送SIGABRT(6)信号该信号默认情况下可以使进程结束
无返回值
*/在使用exit函数或main函数正常退出时如果注册了atexit或on_exit那就会触发退出函数以下是示例代码 #include stdio.h
#include stdlib.hvoid func(void)
{exit(6);
}void goto1(void)
{printf(goto1\n);
}void goto2(int status, void *arg)
{printf(status %d\n, status);printf(arg %s\n, (char *)arg);
}int main(void)
{atexit(goto1); // 退出之前执行goto1on_exit(goto2, heihei); // 退出之前执行goto2可以传参func();return 0;
}/*
相当于钩子函数在退出之前执行可以进行一些回收操作
status 6
arg heihei
goto1
*/回收子进程
如果不回收子进程的话会导致有很多僵尸进程的存在从而消耗更多的系统资源。父进程需要等待子进程到的终止以继续后续工作父进程需要了解子进程终止的原因是正常终止还是异常终止
阻塞回收 wait函数是用于回收子进程的一个函数它使用的是阻塞回收使用它必须包含sys/wait.h头文件 wait函数 pid_t wait(int *status); 功能等待和回收任意子进程 参数status用于输出子进程的终止状态可置NULL 补充可以使用以下工具宏分析子进程的终止状态 if(WIFEXITED(status))// 真printf(正常终止: 进程退出码是%d\n, WEXITSTATUS(status));
else// 假printf(异常终止: 终止进程的信号是%d\n, WTERMSIG(status));// 下面跟上面判断条件相反
if(WIFSIGNALED(status))// 真printf(异常终止: 终止进程的信号是%d\n, WTERMSIG(status));
else// 假printf(正常终止: 进程退出码是%d\n, WEXITSTATUS(status));返回值成功返回回收的子进程PID失败返回-1 简单代码示例 // 子进程的回收
#include stdio.h
#include stdlib.h
#include unistd.h
#include sys/wait.hint main(void)
{// 创建子进程pid_t pid fork();if(pid -1){perror(fork);return -1;}// 子进程相关操作if(pid 0){printf(%d进程: 我是子进程!\n, getpid());// sleep(5);// exit(3);// _exit(5);// return 2;// abort(); // 向进程发送信号异常结束// 以下两句会造成内存无效访问会返回11char *p NULL;*p 123;}// 父进程等待回收子进程printf(%d进程: 我是父进程!\n, getpid());int s; // 用来输出所回收的子进程终止状态pid_t childpid wait(s);if(childpid -1){perror(wait);return -1;}printf(父进程回收了%d进程的僵尸!\n, childpid);// 根据返回值判断子进程是否是正常结束if(WIFEXITED(s))printf(正常结束: %d\n, WEXITSTATUS(s));elseprintf(异常结束: %d\n, WTERMSIG(s));return 0;
}以下代码是一个循环创建5个进程然后父进程挨个回收 #include stdio.h
#include unistd.h
#include sys/wait.h
#include errno.hint main(void)
{printf(%d进程: 我是父进程!\n--------------------------\n, getpid());sleep(1);// 创建子进程for(int i 0; i 5; i){pid_t pid fork();if(pid -1){perror(fork);return -1;}// 子进程操作if(pid 0){printf(%d进程: 我是子进程!\n, getpid());sleep(i1);return i1;}}// 父进程操作: 回收子进程while(1){int s; // 用户接收子进程的终止状态pid_t childpid wait(s);if(childpid -1){if(errno ECHILD){printf(没有子进程可以回收了!\n);break;}else{perror(wait);return -1;}}// 判断子进程的终止状态if(WIFEXITED(s))printf(正常结束: %d\n, WEXITSTATUS(s));elseprintf(异常终止: %d\n, WTERMSIG(s));}return 0;
}非阻塞回收 waitpid函数一般用于非阻塞回收子进程还可以回收特定子进程使用这个函数需要引用sys/wait.h头文件 waitpid函数 pid_t waitpid(pid_t pid, int *status, int options); 功能等待并回收任意或特定子进程参数 pid取-1等待并回收任意子进程相当于wait函数0等待回收特定子进程status用于输出子进程的终止状态可置NULLoption0代表阻塞模式WNOHANG代表非阻塞模式如果等待的进程还在运行则返回0 返回值成功返回回收子进程的PID或者0失败返回-1 以下是使用非阻塞回收的方法回收子进程 #include stdio.h
#include unistd.h
#include stdlib.h
#include sys/wait.h
#include errno.hint main(void)
{printf(%d进程: 我是父进程!\n-------------------------\n, getpid());// 创建子进程for(int i 0; i 5; i){pid_t pid fork();if(pid -1){perror(fork);return -1;}// 子进程相关操作if(pid 0){printf(%d进程: 我是子进程!\n, getpid());sleep(i1);// 三种效果if(i 3){abort();}else if(i 4){char *p NULL;*p 123;}else{return i1;}}}// 父进程回收子进程sleep(1);while(1){int s; // 用于保存进程的终止状态pid_t childpid waitpid(-1, s, WNOHANG); // 这里使用的是非阻塞模式if(childpid -1){// 报错或者没有子进程了if(errno ECHILD){printf(没有子进程了!\n);break;}else{perror(waitpid);return -1;}}else if(childpid 0){// 子进程还在运行printf(子进程在运行无法回收先睡会!\n);sleep(2);}else{// 回收成功并判断是否正常终止printf(%d子进程回收成功!\n, childpid);if(WIFEXITED(s))printf(%d进程正常终止, 进程退出码: %d\n\n, childpid, WEXITSTATUS(s));elseprintf(%d进程异常终止, 终止进程信号: %d\n\n, childpid, WTERMSIG(s));}}return 0;
}/*
代码执行效果
1761797进程: 我是父进程!
-------------------------
1761798进程: 我是子进程!
1761799进程: 我是子进程!
1761800进程: 我是子进程!
1761801进程: 我是子进程!
1761802进程: 我是子进程!
子进程在运行无法回收先睡会!
1761798子进程回收成功!
1761798进程正常终止, 进程退出码: 11761799子进程回收成功!
1761799进程正常终止, 进程退出码: 2子进程在运行无法回收先睡会!
1761800子进程回收成功!
1761800进程正常终止, 进程退出码: 31761801子进程回收成功!
1761801进程异常终止, 终止进程信号: 6子进程在运行无法回收先睡会!
1761802子进程回收成功!
1761802进程异常终止, 终止进程信号: 11没有子进程了!
*/补充
实际情况下无论进程是正常终止还是异常终止都会通过系统内核向其父进程发送一个SIGCHLD(17)信号我们可以提供一个针对该信号的处理函数在信号处理函数中异步的方式回收子进程这样不仅流程简单回收效率还高僵尸进程的存活时间也会很短。 创建新进程
与fork函数不同这里使用的exec函数是创建一个新的进程新进程会取代调用自身的进程新进程覆盖之前的进程地址空间进程的PID不会改变。 exec不是一个函数而是一堆函数功能一样用法相似 #include unistd.h int execl(const char *path, const char *arg, ...); execl(/bin/ls, ls, -a, -l, NULL);
/*
path使用的是路径名
使用NULL作为arg的结尾
失败返回-1成功不返回
*/int execlp(const char *file, const char *arg, ...); execlp(ls, ls, -a, -l, NULL);
/*
file使用的是文件名会从环境变量中一个个的找
使用NULL作为arg的结尾
失败返回-1成功不返回
*/int execle(const char *path, const char *arg, ..., char *const envp[]); char *envp[] {NAMEbhlu, AGE25, NULL};
execle(/usr/bin/env, env, NULL, envp);
/*
比excel多一个envp用于设置环境变量它设置什么新进程的环境变量就只有什么
失败返回-1成功不返回
环境变量输出:NAMEbhluAGE25
*/int execv(const char *path, char *const argv[]); char *argv[] {ls, -a, -l, NULL};
execv(/bin/ls, argv);
/*
execv系列使用的都是字符指针数组字符数组是以NULL结尾
失败返回-1成功不返回
*/int execvp(const char *file, char *const argv[]); char *argv[] {ls, -a, -l, NULL};
execvp(ls, argv);
/*
跟execv差不多就第一个参数是文件名
失败返回-1成功不返回
*/int execve(const char *path, char *const argv[], char *const envp[]); char *argv[] {env, NULL};
char *envp[] {NAMEbhlu, AGE25, NULL};
execve(/usr/bin/env, argv, envp);
/*
跟execle函数差不多就是这里的第二个参数是字符指针数组
失败返回-1成功不返回
*/后缀不同代码的含义也不同 l即list新进程的命令以字符指针列表形式传入列表以空指针结束p即path第一个参数不包含/就根据PATH环境变量搜索文件e即environment设定环境变量不指定则从调用进程复制v即vector新进程的命令行参数以字符指针数组的形式传入数组以空指针结束实际底层最后使用的都是execve函数 使用exec函数基本会将原进程的所有信号、属性、数据等都丢失或者恢复初识状态只有PID、PPID、UID等会被继承下来。 一般都会先创建一个子进程然后在子进程中使用exec函数以下是相关示例 #include stdio.h
#include unistd.hint main(void)
{// 创建子进程pid_t pid fork();if(pid -1){perror(fork);return -1;}// 子进程相关操作if(pid 0){char *argv[] {env, NULL};if(execvp(/bin/env, argv) -1){perror(execvp);return -1;}}// 父进程操作printf(父进程PID: %d\n, getpid());return 0;
}system 下面介绍的是c语言执行shell命令的函数 #include stdlib.h int system(const char *command); 功能执行shell命令参数shell命令如果参数取NULL返回非0表示Shell可用返回0表示不可用返回值成功返回command进程的终止状态 失败返回-1 代码实例 #include stdio.h
#include stdlib.h
#include unistd.hint main(void)
{int s system(echo $PATH);if(s -1){perror(system);return -1;}printf(父进程PID: %d\n, getpid());return 0;
}system函数内部调用了vfork、exec和waitpid等函数而且它是标准库函数可以跨平台使用 如果调用vfork或waitpid函数出错则返回-1如果调用exec函数出错则在子进程中执行exit(127)如果都成功会从waitpid获取command进程的终止状态