一.进程相关概念
1.进程与程序的区分
程序:静态的,如各位所编写的xxx.c文件就是一个程序
进程:动态的,跑起来的程序,如各位游玩的各种游戏
2.如何查看系统中的进程
1.top命令,类似于windows的任务管理器
2.ps命令,显示系统中的全部进程,一般使用ps -aux|grep (想查询的进程名)
3.进程标识符
每一个进程都有一个非负整数作为唯一的表示,叫做进程号
pid = 0:交换进程,用于进程的调度
pid = 1:init,系统初始化
4.获取进程标识符
pid_t getpid(void);获取进程号
pid_t getppid(void);获取父进程的标识号
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
pid_t pid;
pid = getpid();
printf("%d",pid);
return 0;
}
5.父进程与子进程
A进程创建了B进程,A为父进程,B为子进程
6.C语言存储空间分配
代码段:即正文,变量声明和定义变量之外的代码
数据段:初始化的数据
BSS段:未初始化的数据
堆:程序中动态分配的内存空间
栈:如函数调用,函数局部变量,函数返回信息等
二.创建进程
1.fork函数
函数原型:pid_t fork(void);
返回值:调用成功则向父进程返回创建的子进程的进程号,向创建的子进程返回0
调用失败则返回-1
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = fork();
if(pid > 0){
printf("father pid is %d\n",getpid());
}else{
printf("child pid is %d\n",getpid());
}
return 0;
}
2.fork函数创建的子进程与父进程之间的内存关系
子进程与父进程共享同一片内存空间,子进程在修改值变量值时进行写时拷贝,子进程修改变量值不会对父进程的变量值造成影响
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
int data = 10;
pid = fork();
if(pid > 0){
printf("father pid is %d\n",getpid());
}else{
printf("child pid is %d\n",getpid());
data += 100;
}
printf("data = %d\n",data);
return 0;
}
3.vfork函数
函数原型:pid_t vfork(void);
返回值:调用成功则向父进程返回创建的子进程的进程号,向创建的子进程返回0
调用失败则返回-1
include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int cnt = 0;
pid = vfork();
if(pid > 0){
while(1){
printf("this is father pro,pid is %d\n",getpid());
printf("cnt = %d\n",cnt);
sleep(1);
}
}
if(pid == 0){
while(1){
printf("this is child pro,pid is %d\n",getpid());
cnt++;
sleep(1);
if(cnt == 3){
exit(0);
}
}
}
return 0;
}
4.vfork函数创建的子进程与父进程之间的内存关系
子进程直接使用父进程的存储空间,子进程修改变量值时,父进程的变量值也被修改
三.进程退出
1.正常退出
main函数调用return
进程调用exit(),标准C库
进程调用_exit()或_Exit(),属于系统调用
进程最后一个线程返回
进程最后线程调用pthread_exit
2.异常退出
调用abort
当进程收到某些信号时,如Ctrl+C
最后一个线程对取消(cancellation)做出相应
3.等待子进程退出
子进程退出状态不被收集,编程僵死进程(僵尸进程)
等待子进程退出并收集进程的退出状态
pid_t wait(int *status)
pid_t waitpid(pid_t pid,int *status,int options)
参数介绍:
int *status:整数型指针
非空:子进程退出状态放在它所指向的地址中
空:不关心子进程的退出状态
pid:pid == -1:等待任一子进程
pid > 0:等待进程号等于pid的子进程
pid == 0:等待其组ID等于调用进程组ID的任一子进程
pid < 0:等待其组ID等于pid绝对值的任一子进程
options常量:
返回值:回收子进程的pid
调用这两个API可以避免子进程变成僵尸进程
检查wait和waitpid返回的终止状态的宏:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int cnt = 0;
//int status = 10;
pid = fork();
if(pid > 0){
wait(NULL);
// wait(&status);
while(1){
printf("this is father pro,pid is %d\n",getpid());
printf("cnt = %d\n",cnt);
sleep(1);
}
}
if(pid == 0){
while(1){
printf("this is child pro,pid is %d\n",getpid());
cnt++;
sleep(1);
if(cnt == 5){
exit(3);
}
}
}
return 0;
}
孤儿进程:在子进程结束之前,其父进程生命结束,孤儿进程一般由init进程接收
四.exec族函数
来源:linux进程---exec族函数(execl, execlp, execle, execv, execvp, execvpe)-CSDN博客
作用:
我们用fork函数创建新进程后,经常会在新进程中调用exec函数去执行另外一个程序。当进程调用exec函数时,该进程被完全替换为新程序。因为调用exec函数并不创建新进程,所以前后进程的ID并没有改变。
函数原型:
#include <unistd.h>
extern char **environ;
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 execvpe(const char *file, char *const argv[],char *const envp[]);
返回值:
exec函数族的函数执行成功后不会返回,调用失败时,会设置errno并返回-1,然后从原程序的调用点接着往下执行。
参数介绍:
path:可执行文件的路径名字
arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
file:如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。
exec族函数参数极难记忆和分辨,函数名中的字符会给我们一些帮助:
l : 使用参数列表
p:使用文件名,并从PATH环境进行寻找可执行文件
v:应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数。
e:多了envp[]数组,使用新的环境变量代替调用进程的环境变量
perror():解析失败原因并进行输出
#include <unistd.h>
#include <stdio.h>
int main()
{
printf("before exec\n");
if(execl("/bin/ls","ls",NULL) == -1){
printf("exec failed\n");
perror("why:");
}
printf("after exec\n");
return 0;
}
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int data = 0;
int cnt = 0;
while(1){
printf("please input number\n");
scanf("%d",&data);
if(data == 1){
pid = fork();
if(pid > 0){
wait(NULL);
}
if(pid == 0){
if(execl("./changeData","changeData",NULL) == -1){
printf("exec failed\n");
perror("why:");
}
while(1){
printf("do net request\t");
printf("current pro id: %d\n",getpid());
sleep(3);
cnt++;
if(cnt == 3){
exit(3);
}
}
}
printf("data = %d\n",data);
}else{
printf("do nothing\n");
}
}
return 0;
}
五.system函数
函数原型:
int system(const char *command);
返回值:
成功:返回进程的状态值
失败:返回-1
调用sh失败:返回127
linux中的源码:
#include
#include
#include
#include
int system(const char * cmdstring)
{
pid_t pid;
int status;
if(cmdstring == NULL){
return (1);
}
if((pid = fork())<0){
status = -1;
}
else if(pid == 0){
execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
-exit(127); //子进程正常执行则不会执行此语句
}
else{
while(waitpid(pid, &status, 0) < 0){
if(errno != EINTER){
status = -1;
break;
}
}
}
return status;
}
来源:https://www.cnblogs.com/leijiangtao/p/4051387.html
system函数与exec族函数的区别:
system函数调用的命令执行完后还会执行源程序
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int data = 0;
int cnt = 0;
while(1){
printf("please input number\n");
scanf("%d",&data);
if(data == 1){
pid = fork();
if(pid > 0){
wait(NULL);
}
if(pid == 0){
if(system("./changeData") == -1 || system("./changeData") == 127){
printf("exec failed\n");
perror("why:");
}
while(1){
printf("do net request\t");
printf("current pro id: %d\n",getpid());
sleep(3);
cnt++;
if(cnt == 3){
exit(3);
}
}
}
printf("data = %d\n",data);
}else{
printf("do nothing\n");
}
}
return 0;
}
六.popen函数
函数原型:
FILE *popen(const char *command, const char *type);
参数介绍:
type:“r”,返回值链接command的标准输出
“w”,返回值链接command的标准输入
作用:获取调用命令的返回值
#include <stdio.h>
#include <stdlib.h>
int main()
{
FILE *fp;
char str[1024] = {0};
int n_read = 0;
fp = popen("ps","r");
n_read = fread(str,1,1024,fp);
printf("read %d byte,content:\n%s",n_read,str);
return 0;
}