文章目录
进程基础
进程相关的基本概念
进程: 进程是一个独立的可调度的任务
- 进程是一个抽象实体。当系统在执行某个程序时,分配和释放的各种资源
- 进程是一个程序的一次执行的过程
进程和程序的区别:
- 程序是静态的,它是一些保存在磁盘上的指令的有序集合,没有任何执行的概念
- 进程是一个动态的概念,它是程序执行的过程,包括创建、调度和消亡
进程是程序执行和资源管理的最小单位
进程与程序
程序主要构成:正文段(只读,共享)、数据段(虚拟地址)
进程主要构成:正文段、数据段、堆栈段、PCB(描述进程信息的一个结构体)(实际分配物理地址)
Linux 下的进程结构
主要的进程标识
- 进程号(Process Identity Number, PID)
- 父进程号(Parent Process ID, PPID)
PID 唯一地标识一个进程
Linux中的进程包含三个段
- “数据段”存放的是全局变量、常数以及动态数据分配的数据空间(如
malloc
函数取得的空间)等。 - “正文段”存放的是程序中的代码
- “堆栈段”存放的是函数的返回地址、函数的参数以及程序中的局部变量
Linux 系统中的进程类型
- 交互进程:该类进程是由 shell 控制和运行的。交互进程既可以在前天运行,也可以在后台运行。
- 批处理进程:该类进程不属于某个终端,它被提交到一个队列中以便顺序执行。
- 守护进程:该类进程在后台运行。它一般在Linux启动时开始执行,系统关闭时才结束。
进程运行状态
-
运行态(就绪态):此时进程或者正在运行,或者准备运行。
-
等待态:此时进程在等待一个事件的发生或某种系统资源。
-
- 可中断
- 不可中断
-
停止态:此时进程被终止。
-
僵尸态:这是一个已终止的进程,但还在进程向量数组中占有一个
task_struct
结构。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4DyLE3eI-1618745993580)(.\images\进程运行状态.png)]
进程的模式
进程的执行模式分为用户模式和内行模式
调度进程
ps
查看系统中的进程ps axj
:查看进程的信息top
:动态显示进程信息nice
按用户指定的优先级运行程序nice -5 /a.out
: 以nice
值为 5 的优先级方式运行a.out
nice --5 ./a.out
: 以nice
值为-5
的优先级方式运行a.out
nice
值为(-20
~19
,值越低,优先级越高)
renice
:改变正在运行进程的优先级renice -5 PID
:改变正在运行进程的nice
值,设为-5
renice 5 PID
:改变正在运行进程的nice
值,设为5
kill
:结束进程(包括后台进程)kill -9 pid
杀死一个进程bg
:将挂起的进程在后台执行ctrl Z
:挂起当前程序- 通过
jobs
查看后台运行或者挂起进程对应的号码 - 然后通过
bg
对应的号码来使挂起的进程放入后台执行
fg
:把后台的进程放到前台运行- 通过
fg
对应的号码,把后台运行的进程放到前台运行
进程的相关系统调用
create a child process - fork
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(int argc, const char *argv[]){
pid_t pid;
pid = fork();
if(pid == -1){
perror("fail to fork");
exit(1);
}else if(pid == 0){
/*子进程*/
printf("I'm child:%d->%d\n",getpid(),getppid());
}else{
/*父进程*/
printf("I'm parent:%d->%d\n",getpid(),getppid());}
printf("cccccc\n");
return 0;}
- 系统为每一个进程都分配一段 0~4 G 的虚拟地址空间
- 写时拷贝技术:它通过允许父子进程可访问相同物理内存从而伪装了对进程地址空间的真实拷贝,当子进程需要改变内存中数据时才拷贝父进程。也就是说当子进程对数据进行更改操作时,系统才会为更改的数据分配实际的物理地址空间。
pid_t getpid(void)
:函数返回调用进程本身的PID
pid_t getppid(void)
:函数返回调用进程的父进程的PID
execute a file - execl
, execlp
, execle
, execv
, execvp
, execvpe
fork
函数用于创建一个子进程,该子进程几乎拷贝了父进程的全部内容。
exec
函数族提供了一种在进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段。在执行完之后,原调用进程的内容除了进程号外,其他全部都被替换了
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(int argc, const char *argv[]){
/*
if(execl("test","abc","def","xxx",NULL) == -1){
perror("fail to execl");
exit(1);}
*/
/* char *buf[] = {"11","-l","-a",NULL};
if(execvp("ls",buf) == -1){
perror("fail to execv");
exit(1);}
*/
char *buf[] = {NULL};
char *env[] = {"XXXX=HELLO",NULL};
if(execvpe("/home/linux/g17091/process/day04/exec/test",buf,env) == -1){
perror("fail to execvpe");
exit(1);}
printf("succcess to exec\n");
return 0;}
exit
和 _exit
exit
进程退出函数,会释放资源将缓存区内容输出(进行善后工作)
_exit
进程退出函数,将资源全部清空释放
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wfNe78Om-1618745993582)(.\images\exit.png)]
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<unistd.h>
int main(int argc, const char *argv[]){
FILE *fp = fopen("test.txt","w+");
if(NULL == fp){
printf("fail to fopen : %s\n",strerror(errno));
perror("fail to fopen");
exit(1);}
printf("success to fopen\n");
fprintf(fp,"hello kitty\n");
// fflush(fp);
fclose(fp);
while(1);
_exit(1);
return 0;}
#include <stdlib.h>
#include <stdio.h>
int main(){
printf("this process will exit!\n");
exit(0);
printf("never be displayed!\n");}
#include <stdlib.h>
#include <stdio.h>
int main(){
printf("Using exit...\n");
printf("This is the end");
exit(0);}
#include <stdio.h>
#include <unistd.h>
int main(){
printf("Using exit...\n");
printf("This is the end");
_exit(0);}
linux@ubuntu64-vm:~/文档/IO_process/process$ ./a.out
status: 3
wait for process to change state - wait
和 waitpid
#include <sys/wait.h>
#include<stdio.h>
#include<stdlib.h>
int main(){
pid_t pid;
pid = fork();
if(pid == -1){
perror("fail to fork");
exit(1);
}else if(pid == 0)
exit(3);
else{
int status;
wait(&status);
printf("status: %d\n", WEXITSTATUS(status));} //返回退出时的数值
return 0;}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, const char *argv[]){
char buf[32];
long offset = 0;
int fd_s, fd_t, count;
if(argc != 3){
printf("cmd + srcfile + desfile\n");
exit(1);}
//1.打开源文件
fd_s = open(argv[1], O_RDONLY);
//2.打开目标文件
fd_t = open(argv[2], O_RDWR|O_CREAT, 0666);
//获取中间位置偏移量
offset = lseek(fd_s, 0, SEEK_END) / 2;
printf("%ld\n", offset);
pid_t pid;
pid = fork();
if(pid == -1){
perror("fail to fork");
exit(1);
}else if(pid){
/*父进程*/
printf("im parent:%d->%d\n",getpid(),getppid());
lseek(fd_s, offset, SEEK_SET);
lseek(fd_t, offset, SEEK_SET);
while(count = read(fd_s, buf, 32))
write(fd_t, buf, count);
}else{
/*子进程*/
printf("im child:%d->%d\n",getpid(),getppid());
//重劈文件指针,避免混用
close(fd_s);
close(fd_t);
fd_s = open(argv[1],O_RDONLY);
fd_t = open(argv[2],O_WRONLY);
lseek(fd_s, 0, SEEK_SET);
lseek(fd_t, 0, SEEK_SET);
while(count = read(fd_s, buf, 32<offset?32:offset)){
write(fd_t, buf, count);
offset -= count;}}
return 0;}
守护进程
守护进程
- 也就是通常所说的 Daemon 进程,是 Linux 中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件
- 守护进程常常在系统启动时开始运行,在系统关闭时终止
- Linux 系统有很多守护进程,大多数服务都是用守护进程实现的
- 在 Linux 中,每一个系统与用户进行交流的界面称为终端。从该终端开始运行的进程都会依附于这个终端,这个终端称为这些进程的控制终端。当控制终端被关闭时,相应的进程都会被自动关闭。
- 守护进程能够突破这种限制,它从开始运行,直到整个系统关闭才会推出。如果想让某个进程不会因为用户或终端的变化而受到影响,就必须把这个进程变成一个守护进程。
创建守护进程的步骤
-
创建子进程,父进程退出
- 此时,子进程为孤儿进程,与终端脱离的部分关系
-
在子进程中创建新会话 =>
setsid()
- 此时,子进程为新建会话组的组长,彻底脱离于原 bash 会话组的关系
-
改变当前目录为根目录 =>
chdir(“\”)
; -
保证守护进程工作环境的安全性
- 重设
umask
码为0
,不屏蔽任何权限 =>umask(0)
; - 关闭终端相关的文件描述符 0,1,2 =>
close(0); close(1); close(2);
- 重设