当前位置: 首页 > news >正文

C语言-进程控制编程

1、 进程的基本概念

进程的分类
    交互进程
    批处理进程
    守护进程:一般在后台运行,一般由操作系统在开机时通过脚本自动激活启动或由超级管理用户root来启动

进程的属性
    进程ID:进程的唯一数值,用来区分进程
    启动进程的用户ID(UID)和归属的组(GID)
    进程状态:运行(R)、休眠(S)、僵尸(Z)
    进程执行优先级
    进程所连接的终端名
    进程资源占用

进程监视工具:ps
    一般用法:ps -aux|more    //用管道和more命令连接起来分页查看
    其他用法:ps aux|grep 程序名  //提取指定程序的进程

查询进程工具:pgrep
    ps 参数选项 程序名
    常用参数如下:
        -1: 列出程序名和进程ID
        -o: 进程起始的ID
        -n: 进程终止的ID

Linux进程的三态
    就绪状态:若进程已被分配到除CPU以外所有必要的资源,只要获得处理器便可立即执行
    执行状态:进程已获得处理器,其进程正在处理器上执行
    阻塞状态:当正在执行的进程,由于等待某个事件发生而无法执行时,便处于阻塞状态

Linux进程调度算法
    FCFS算法:也叫FIFO算法,先来先处理
    时间轮片算法:对FIFO算法的改进,周期性的进行进程切换
    STCF算法:短任务优先算法,核心是所有程序都有一个优先级,短任务的优先级比长任务的高,OS总是安排优先级高的进程先运行

2、 进程创建

2.1 getgid函数和getpid()函数

函数详解

表头文件#include <unistd.h>#include <sys/types.h>定义函数gid_t getgid(void)函数说明用来获得执行目前进程的组识别码返回值组识别码(进程的ID)pid_t getpid(void)
获取当前进程ID

2.2 getppid函数

函数详解 

表头文件#include <unistd.h>定义函数pid_t getppid(void)函数说明获得目前进程的父进程识别码返回值目前进程的父进程识别码

综合案例 

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>int main(int argc,char *argv[])
{printf("My parent 'pid=%d'\n",getppid());printf("My 'pid=%d'\n",getpid());return 0;
}

运行结果

linux@ubuntu:~/test$ gcc get_dome.c -o get_dome -Wall
linux@ubuntu:~/test$ ./get_dome
My parent 'pid=14743'
My 'pid=14846'

2.3 fork函数(创建子进程)

 函数详解

表头函数#include <sys/types.h>#include <unistd.h>定义函数pid_t fork(void)函数说明创建子进程,子进程完全复制父进程的资源,复制出的子进程有自己的task_struct结构和PID,复制父进程其他所有的资源现在Linux中采用写时复制技术,即并不会立即复制,若后来确实发生了写入,父子进程的数据不一致,于是产生了复制动作,若直接调用exec执行另一个可执行文件,那将不会进行复制父子进程谁先运行不确定,进行是是同步的返回值在父进程中返回子进程号,在子进程中返回0,错误返回-1

 综合案例 

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>int main(int argc,char *argv[])
{pid_t child;int i = 0;/*创建子进程*/if((child = fork()) == -1){printf("fork ERRO\n");exit(1);}else if(child == 0){printf("my is child:%d\n",getpid());i = 18;printf("child i exit with %d\n",i);}else{printf("my is father:%d\n",getpid());i = 41;sleep(1);printf("father i exit with %d\n",i);}printf("father and child have %d\n",getpid());return 0;
}

 运行结果

linux@ubuntu:~/test$ gcc fork_dome.c -o fork_dome -Wall
linux@ubuntu:~/test$ ./fork_dome 
my is father:15057               //父进程执行打印,给i赋值,然后睡眠
my is child:15058                //子进程执行打印
child i exit with 18             //子进程给i赋值,打印
father and child have 15058      //子进程出了if判断后的打印
father i exit with 41            //父进程睡眠完了,虽然子进程给i另外赋值了,但fork创建的进程有自己独立的空间,所以子进程i的赋值不影响父进程
father and child have 15057
linux@ubuntu:~/test$ 

2.4 vfork函数

函数详解 

表头函数#include <unistd.h>定义函数pid_t vfork(void)函数说明创建子进程,与fork不同的是,进程是调用进程的一个副本,共享父进程的地址空间,它会保证子进程先运行,子进程运行的时候会暂停父进程的执行,直到子进程调用子进程调用exec族,或者exit()结束,父进程才会继续执行返回值成功在父进程会返回新建立的子进程代码(PID),而在新建立的子进程中返回0。如果失败直接返回-1

  综合案例 

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>int main(int argc,char *argv[])
{pid_t child;int i = 0;/*创建子进程*/if((child = vfork()) == -1){printf("fork ERRO\n");exit(1);}else if(child == 0){printf("my is child:%d\n",getpid());i = 18;sleep(1);printf("child i exit with %d\n",i);exit(1);}else{printf("my is father:%d\n",getpid());sleep(2);printf("father i exit with %d\n",i);    //i的值会因为子进程的中的改变而改变,在fork中会是0}printf("father and child have %d\n",getpid());return 0;
}

 运行结果

linux@ubuntu:~/test$ gcc vfork_dome.c -o vfork_dome -Wall
linux@ubuntu:~/test$ ./vfork_dome 
my is child:15154                  //先执行子进程,父进程挂起
child i exit with 18
my is father:15153
father i exit with 18              //父进程会因为子进程改变变量而改变
father and child have 15153

2.5 启动进程:exec族

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[]);  //真正意义上的系统调用,其他都是再次基础上封装

2.5.1 execl函数

 函数详解

头文件#include <unistd.h>定义函数int execl(const char *path,const char *arg,...)函数说明path 函数所代表的文件路径 接下的代表执行该文件传送过去的参数返回值执行成功函数不会返回,执行失败则返回-1

2.5.2 execlp函数

函数详解 

头文件#include <unistd.h>定义函数int execlp(const char *file,const char *arg,...)函数说明从PATH环境变量所指目录中查找符合参数file的文件名 接下来的代表执行该文件传送过去的参数返回值成功不会返回 执行失败则直接返回-1

综合案例 

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int main(int argc,char *argv[])
{pid_t child;if((child = fork()) < 0){printf("vfork error!\n");exit(-1);}if(child == 0){printf("my is child\n");if(-1 == execlp("./a","hello","nihao",NULL)){printf("execlp error!\n");}}else{printf("my is father,a.c comletion\n");}return 0;
}

 运行结果

linux@ubuntu:~/test$ gcc a.c -o a -Wall
linux@ubuntu:~/test$ ./exec_dome
my is father,a.c comletion
my is child
linux@ubuntu:~/test$ 我是a程序,将打印传入的参数.
0:hello
1:nihao

2.5.3 execve函数(真正意义上的系统调用)

表头文件#include <unistd.h>定义函数int execve(const char *path,char * const argv[],char *const envp[])函数说明filename 文件路径包含文件名参数利用数组指针来传递给执行文件 末尾为(char*)0传递给执行文件新的环境变量,末尾为0返回值成功不会返回 执行失败则直接返回-1

2.5.4 execvp函数

表头文件#include <unistd.h>定义函数int execvp(const char *file,char * const argv[])函数说明从PATH环境变量所指目录中查找符合参数file的文件名argv数组  存储可执行文件执行时需要的参数返回值成功不会返回 执行失败则直接返回-1

2.6 system函数

函数详解  

头文件#include <stdlib.h>定义函数int system(const char * string)函数说明用于执行shell命令system()会调用fork()产生子进程,由子进程调用/bin/sh-c string来执行参数string字符串所代表的命令,此命令执行完后随即返回原调用的进程在system()调用期间,SIGCHLD信号会被暂时搁置,SIGINT和SIGOUIT信号则会被忽略返回值当在调用/bin/sh时失败返回127,其他原因失败返回-1,若string为空指针,返回非零值

 综合案例 

#include <stdlib.h>int main(int argc,char *argv[])
{system("ls -al");return 0;
}

运行结果

linux@ubuntu:~/test$ gcc system_dome.c -o system_dome
linux@ubuntu:~/test$ ./system_dome 
total 92
drwxrwxr-x  2 linux linux  4096  9月 27 16:09 .
drwxr-xr-x 32 linux linux  4096  9月 27 14:23 ..
-rwxrwxr-x  1 linux linux  7193  9月 27 14:22 a
-rw-rw-r--  1 linux linux   197  9月 27 14:22 a.c
-rwxrwxr-x  1 linux linux  7273  9月 27 14:20 exec_dome
-rw-rw-r--  1 linux linux   382  9月 27 14:23 exec_dome.c
-rwxrwxr-x  1 linux linux  7348  9月 27 13:23 fork_dome
-rw-rw-r--  1 linux linux   543  9月 27 13:23 fork_dome.c
-rwxrwxr-x  1 linux linux  7241  9月 27 12:58 get_dome
-rw-rw-r--  1 linux linux   188  9月 27 12:59 get_dome.c
-rwxrwxr-x  1 linux linux  7167  9月 27 16:09 system_dome
-rw-rw-r--  1 linux linux    87  9月 27 16:09 system_dome.c
-rw-------  1 linux linux 12288  9月 27 16:09 .system_dome.c.swp
-rwxrwxr-x  1 linux linux  7350  9月 27 13:52 vfork_dome
-rw-rw-r--  1 linux linux   557  9月 27 13:53 vfork_dome.c

2.7 wait函数和waitpid函数

 函数详解  

头文件#include <sys/types.h>#include <sys/wait.h>定义函数pid_t wait(int *status)函数说明会暂停目前进程的执行,直到有信号到来或子进程结束为止。status  返回子进程的结束状态值,可设置为NULL通过WEXITSTATUS(status)来获取结束值通过WIFEXITED(status) 判断是否为正常结束,正常结束为非0值返回值执行成功返回值子进程识别码(PID),错误返回-1

 综合案例 

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>int main(int argc,char *argv[])
{pid_t pid;int status,i;if(fork() == 0){printf("tis is the child process .pid=%d\n",getpid());exit(5);}else{sleep(1);printf("this is the parent process.wait for child...\n");pid=wait(&status);i = WEXITSTATUS(status);printf("child's pid=%d.eixt status=%d\n",pid,i);}return 0;
}

 运行结果

linux@ubuntu:~/test$ gcc wait_dome.c -o wait_dome
linux@ubuntu:~/test$ ./wait_dome 
tis is the child process .pid=24163
this is the parent process.wait for child...
child's pid=24163.eixt status=5

 函数详解  

头文件#include <sys/types.h>#include <sys/wait.h>定义函数pid_t waitpid(pid_t pid,int *status,int options)函数说明用于等待指定ID子进程中断或结束pid 欲等待的子进程识别码pid<-1:等待进程组识别码为pid绝对值的任何进程pid=-1:等待任何子进程,相当于wait函数pid=0: 等待进程组识别与目前进程相同的任何子进程pid>0: 等待任何子进程识别码为pid的子进程status  返回子进程的结束状态值options 为0或下面数值的OR运算(|)组合WNOHANG: 没有任何已经结束的子进程则马上返回,不予等待WUNTRACED: 如果子进程进入暂停执行状态则马上返回,但结束状态不予理会返回值执行成功返回子进程识别码(PID),错误返回-1

2.8 exit函数和_exit函数

表头文件#include <stdlib.h>定义函数void exit(int status)函数说明用于正常结束目前进程的执行,并把参数status返回给父进程而所有的缓冲区数据会自动写回并关闭未关闭的文件
​
返回值无
_exit同exit函数
不同点:传递SIGCHLD信号给父进程,不处理标准I/O缓冲区

3、 守护进程

3.1 守护进程的基本概念

进程组:一个或多个进程的集合,每个进程都有一个进程组ID,这个ID就是进程组长的ID
会话期:一个或多个进程组的集合,每个会话有唯一一个会话首进程,会话ID为会话进程ID
控制终端:每一个会话可以有一个单独的控制终端,与控制终端连接的会话首进程就是控制进程创建守护进程过程中用到的一个关键函数——setsid#include <unistd.h>pid_t setsid(void);用于创建一个新的会话期,实现以下效果:摆脱原会话的控制,该进程变成新会话期的首进程摆脱原进程组,称为一个新进程组的组长摆脱终端控制

3.2 创建守护进程的一般步骤

(1) 通过fork函数创建子进程,父进程通过exit函数退出
(2) 在子进程中调用setsid函数,创建新的会话
(3) 再次通过fork函数创建一个子进程并让父进程退出
(4) 在子进程中调用chdir函数,让根目录“/”称为子进程的工作目录
(5) 在子进程中调用umask函数,设置进程的文件权限掩码为0
(6) 在子进程中关闭任何不需要的文件描述符
(7) 守护进程退出处理(结束)
​
利用库函数deamon创建守护进程
#include <unistd.h>
int daemon(int nochdir,int noclose);

 综合案例 

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>static int flag = 1;
void create_deamon();                   //守护进程创建函数
void handler(int);                      //接受信号结束句柄int main(int argc,char *argv[])
{time_t t;int fd;create_deamon();struct sigaction act;act.sa_handler = handler;sigemptyset(&act.sa_mask);act.sa_flags = 0;if(sigaction(SIGQUIT,&act,NULL)){printf("sigaction error.\n");exit(0);}while(flag){fd = open("/home/linux/test/dae.log",O_WRONLY|O_CREAT|O_APPEND,0644);if(fd == -1){printf("open error\n");}t = time(0);char *buf = asctime(localtime(&t));write(fd,buf,strlen(buf));close(fd);sleep(10);}return 0;
}void handler(int sig)
{printf("I got a signal %d\nI'm quitting.\n",sig);flag = 0;
}void create_deamon(){              //守护进程创建函数pid_t pid;pid = fork();if(pid == -1){printf("fork error\n");exit(1);}else if(pid){exit(0);}if(-1 == setsid()){printf("setsid error\n");exit(1);}pid = fork();if(pid == -1){printf("fork error\n");exit(1);}else if(pid){exit(0);}chdir("/");int i;for(i = 0;i < 3;++i){close(i);}umask(0);return;
}

 运行结果

linux@ubuntu:~/test$ gcc deamon_dome.c -o deamon_dome -Wall
linux@ubuntu:~/test$ ./deamon_dome 
linux@ubuntu:~/test$ cat dae.log
Sun Sep 29 13:22:50 2024
Sun Sep 29 13:23:00 2024
Sun Sep 29 13:23:10 2024
linux@ubuntu:~/test$ ps -ef|grep 'deamon_dome'
linux    24622     1  0 13:22 ?        00:00:00 ./deamon_dome

相关文章:

  • ceph rgw 桶分片之reshard
  • The Sandbox 游戏制作教程第 6 章|如何使用装备制作出色的游戏 —— 避免环境危险
  • 数据库 - python操作MySQL
  • 【C语言】tcp接收服务
  • 使用 Spring Boot 实现 JWT 生成与验证的自定义类
  • Library介绍(三)
  • 二叉搜索树(c++版)
  • Qt多线程与数据库
  • MacOS升级Ruby版本详解:步骤、挑战与解决方案
  • 深度学习推理的技术实现与优化策略
  • ELK-03-skywalking监控linux系统
  • 新能源汽车储充机器人:能源高效与智能调度
  • STM32常见配置
  • LM393 电压比较器和典型电路
  • Ubuntu 镜像替换为阿里云镜像:简化你的下载体验
  • -------------------- 第二讲-------- 第一节------在此给出链表的基本操作
  • 【MySQL经典案例分析】 Waiting for table metadata lock
  • 【跃迁之路】【669天】程序员高效学习方法论探索系列(实验阶段426-2018.12.13)...
  • 10个确保微服务与容器安全的最佳实践
  • Fabric架构演变之路
  • JavaScript创建对象的四种方式
  • Nginx 通过 Lua + Redis 实现动态封禁 IP
  • rabbitmq延迟消息示例
  • spring cloud gateway 源码解析(4)跨域问题处理
  • Vue 2.3、2.4 知识点小结
  • 从@property说起(二)当我们写下@property (nonatomic, weak) id obj时,我们究竟写了什么...
  • 基于遗传算法的优化问题求解
  • 漂亮刷新控件-iOS
  • 浅谈Kotlin实战篇之自定义View图片圆角简单应用(一)
  • 区块链将重新定义世界
  • 如何打造100亿SDK累计覆盖量的大数据系统
  • 双管齐下,VMware的容器新战略
  • 【云吞铺子】性能抖动剖析(二)
  • Linux权限管理(week1_day5)--技术流ken
  • 如何用纯 CSS 创作一个货车 loader
  • ​​​​​​​​​​​​​​Γ函数
  • ###STL(标准模板库)
  • #07【面试问题整理】嵌入式软件工程师
  • #中的引用型是什么意识_Java中四种引用有什么区别以及应用场景
  • (06)金属布线——为半导体注入生命的连接
  • (13):Silverlight 2 数据与通信之WebRequest
  • (a /b)*c的值
  • (C语言)输入自定义个数的整数,打印出最大值和最小值
  • (ISPRS,2021)具有遥感知识图谱的鲁棒深度对齐网络用于零样本和广义零样本遥感图像场景分类
  • (LLM) 很笨
  • (第30天)二叉树阶段总结
  • (十一)JAVA springboot ssm b2b2c多用户商城系统源码:服务网关Zuul高级篇
  • (数据结构)顺序表的定义
  • (小白学Java)Java简介和基本配置
  • (转)Scala的“=”符号简介
  • (转)重识new
  • (转载)OpenStack Hacker养成指南
  • .net core 调用c dll_用C++生成一个简单的DLL文件VS2008
  • .net core 微服务_.NET Core 3.0中用 Code-First 方式创建 gRPC 服务与客户端
  • .net dataexcel winform控件 更新 日志