C语言-进程控制编程

news/2024/12/22 0:21:03/

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


http://www.ppmy.cn/news/1534062.html

相关文章

RabbitMQ高级特性-发送方确认

对于发送方发送消息到RabbitMQ的可靠性机制 引入:在持久化的消息正确存⼊RabbitMQ之后,还需要有⼀段时间(虽然很短,但是不可忽视)才能存⼊磁盘中.RabbitMQ并不会为每条消息都进⾏同步存盘(调⽤内核的fsync⽅法)的处理, 可能仅仅保存到操作系统缓存之中⽽不是物理磁盘之中. 如…

Redis-主从复制

分布式系统,涉及到一个非常关键的问题:单点问题 如果某个服务器程序,只有一个节点,就会出现: 可用性问题(这个服务器挂了,服务中断)性能/支持的并发量有限 引入分布式系统,主要也是为了解决上述的单点问题 在分布式系统中,希望有多个服务器来部署redis服务,从而构成一个red…

【JAVA开源】基于Vue和SpringBoot的校园资料分享平台

本文项目编号 T 059 &#xff0c;文末自助获取源码 \color{red}{T059&#xff0c;文末自助获取源码} T059&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 查…

游戏如何对抗改包

游戏改包是指通过逆向分析手段及修改工具&#xff0c;来篡改游戏包内正常的设定和规则的行为&#xff0c;游戏包被篡改后&#xff0c;会被植入/剔除模块进行重打包。 本期图文我们将通过实际案例分析游戏改包的原理&#xff0c;并分享游戏如何应对改包问题。 安卓平台常见的改…

django创建一个新的应用

使用 python manage.py startapp myapp 命令可以在你的 Django 项目中创建一个新的应用&#xff0c;名为 myapp。应用是 Django 项目的组成部分&#xff0c;可以帮助你组织代码和功能。执行该命令后&#xff0c;会在你的项目目录下创建一个名为 myapp 的文件夹&#xff0c;包含…

【AIGC】内容创作——AI文字、图像、音频和视频的创作流程

我的主页&#xff1a;2的n次方_ 近年来&#xff0c;生成式人工智能&#xff08;AIGC&#xff0c;Artificial Intelligence Generated Content&#xff09;技术迅速发展&#xff0c;彻底改变了内容创作的各个领域。无论是文字、图像、音频&#xff0c;还是视频&#xff0c;A…

JSON字符串转换成Java集合对象

在Java中&#xff0c;将JSON字符串转换成Java集合对象通常涉及到使用JSON处理库&#xff0c;如Jackson或Google的Gson。以下是使用这两个库的示例&#xff1a; 使用Jackson 添加Jackson依赖&#xff1a;如果你使用Maven&#xff0c;可以在pom.xml文件中添加以下依赖&#xff1…

数据结构双向链表和循环链表

目录 一、循环链表二、双向链表三、循环双向链表 一、循环链表 循环链表就是首尾相接的的链表&#xff0c;就是尾节点的指针域指向头节点使整个链表形成一个循环&#xff0c;这就弥补了以前单链表无法在后面某个节点找到前面的节点&#xff0c;可以从任意一个节点找到目标节点…