【进程篇】03.进程的概念与基本操作

时间:2024-12-18 17:24:24

一、进程的概念与理解

1.1 概念

进程是程序的一个执行实例,即正在执行的程序。

1.2 理解

我们编写代码运行后会在磁盘中会形成一个可执行程序,当我们运行这个可执行程序时,这个程序此时就会被操作系统的调度器加载到内存中;操作系统会对进程进行管理(进程的加载、调度、切换、释放……),而我们可能同时启动多个进程(一面听歌一面看PPT),那么操作系统是如何管理进程的呢?

很简单,先描述,后组织,即我们的操作系统对进程的属性进行管理,创建了内核级的数据结构PCB(Linux中的PCB是task_struct)来管理进程。因此实际上进程=内核数据结构(task_struct)+代码和数据。接下来这些task_struct彼此相连构成了struct task_struct* list,而后操作系统就可以对多个进程基于时间片进行轮转调度、切换等等。在这一过程中呈现了动态的特征,因此我们有了如上的结论:进程是运行起来的程序

1.3 源码中对task_struct的描述 

下面是来自Linux0.11版本的源码中对task_struct的描述:

struct task_struct {
/* these are hardcoded - don't touch */
	long state;	/* -1 unrunnable, 0 runnable, >0 stopped */
	long counter;
	long priority;
	long signal;
	struct sigaction sigaction[32];
	long blocked;	/* bitmap of masked signals */
/* various fields */
	int exit_code;
	unsigned long start_code,end_code,end_data,brk,start_stack;
	long pid,father,pgrp,session,leader;
	unsigned short uid,euid,suid;
	unsigned short gid,egid,sgid;
	long alarm;
	long utime,stime,cutime,cstime,start_time;
	unsigned short used_math;
/* file system info */
	int tty;		/* -1 if no tty, so it must be signed */
	unsigned short umask;
	struct m_inode * pwd;
	struct m_inode * root;
	struct m_inode * executable;
	unsigned long close_on_exec;
	struct file * filp[NR_OPEN];
/* ldt for this task 0 - zero 1 - cs 2 - ds&ss */
	struct desc_struct ldt[3];
/* tss for this task */
	struct tss_struct tss;
};

二、进程的基本操作

查看进程信息的指令:ps axj

2.1 查看PID

PID是用以区分进程唯一性的编号,我们可以使用系统接口getpid来获取当前进程的pid。

#include<stdio.h>     
#include<sys/types.h> 
#include<unistd.h>      
int main()      
{      
    while(1)      
    {      
        printf("I am a process,My Pid is %d\n",getpid());      
        sleep(1);      
    }  
    return 0;  
}     

终止该进程的方式:1. Crtl+C可以结束进程      2.发送信号 kill -9 5150也可以杀死该进程

仅仅通过ps axj显示的进程信息是有些少的,进程的信息被保存在/proc中,以自己的PID为文件名,因此我们也可以通过查看/proc/PID来查看进程的信息

[caryon@VM-24-10-centos ~]$ ll /proc/9139
total 0
dr-xr-xr-x 2 caryon caryon 0 Oct  9 15:47 attr
-rw-r--r-- 1 caryon caryon 0 Oct  9 15:47 autogroup
-r-------- 1 caryon caryon 0 Oct  9 15:47 auxv
-r--r--r-- 1 caryon caryon 0 Oct  9 15:47 cgroup
--w------- 1 caryon caryon 0 Oct  9 15:47 clear_refs
-r--r--r-- 1 caryon caryon 0 Oct  9 15:47 cmdline
-rw-r--r-- 1 caryon caryon 0 Oct  9 15:47 comm
-rw-r--r-- 1 caryon caryon 0 Oct  9 15:47 coredump_filter
-r--r--r-- 1 caryon caryon 0 Oct  9 15:47 cpuset
lrwxrwxrwx 1 caryon caryon 0 Oct  9 15:47 cwd -> /home/caryon/linux/lesson11
-r-------- 1 caryon caryon 0 Oct  9 15:47 environ
lrwxrwxrwx 1 caryon caryon 0 Oct  9 15:47 exe -> /home/caryon/linux/lesson11/code
dr-x------ 2 caryon caryon 0 Oct  9 15:47 fd
dr-x------ 2 caryon caryon 0 Oct  9 15:47 fdinfo
-rw-r--r-- 1 caryon caryon 0 Oct  9 15:47 gid_map
-r-------- 1 caryon caryon 0 Oct  9 15:47 io
-r--r--r-- 1 caryon caryon 0 Oct  9 15:47 limits
-rw-r--r-- 1 caryon caryon 0 Oct  9 15:47 loginuid
dr-x------ 2 caryon caryon 0 Oct  9 15:47 map_files
-r--r--r-- 1 caryon caryon 0 Oct  9 15:47 maps
-rw------- 1 caryon caryon 0 Oct  9 15:47 mem
-r--r--r-- 1 caryon caryon 0 Oct  9 15:47 mountinfo
-r--r--r-- 1 caryon caryon 0 Oct  9 15:47 mounts
-r-------- 1 caryon caryon 0 Oct  9 15:47 mountstats
dr-xr-xr-x 5 caryon caryon 0 Oct  9 15:47 net
dr-x--x--x 2 caryon caryon 0 Oct  9 15:47 ns
-r--r--r-- 1 caryon caryon 0 Oct  9 15:47 numa_maps
-rw-r--r-- 1 caryon caryon 0 Oct  9 15:47 oom_adj
-r--r--r-- 1 caryon caryon 0 Oct  9 15:47 oom_score
-rw-r--r-- 1 caryon caryon 0 Oct  9 15:47 oom_score_adj
-r--r--r-- 1 caryon caryon 0 Oct  9 15:47 pagemap
-r-------- 1 caryon caryon 0 Oct  9 15:47 patch_state
-r--r--r-- 1 caryon caryon 0 Oct  9 15:47 personality
-rw-r--r-- 1 caryon caryon 0 Oct  9 15:47 projid_map
lrwxrwxrwx 1 caryon caryon 0 Oct  9 15:47 root -> /
-rw-r--r-- 1 caryon caryon 0 Oct  9 15:47 sched
-r--r--r-- 1 caryon caryon 0 Oct  9 15:47 schedstat
-r--r--r-- 1 caryon caryon 0 Oct  9 15:47 sessionid
-rw-r--r-- 1 caryon caryon 0 Oct  9 15:47 setgroups
-r--r--r-- 1 caryon caryon 0 Oct  9 15:47 smaps
-r--r--r-- 1 caryon caryon 0 Oct  9 15:47 stack
-r--r--r-- 1 caryon caryon 0 Oct  9 15:47 stat
-r--r--r-- 1 caryon caryon 0 Oct  9 15:47 statm
-r--r--r-- 1 caryon caryon 0 Oct  9 15:47 status
-r--r--r-- 1 caryon caryon 0 Oct  9 15:47 syscall
dr-xr-xr-x 3 caryon caryon 0 Oct  9 15:47 task
-r--r--r-- 1 caryon caryon 0 Oct  9 15:47 timers
-rw-r--r-- 1 caryon caryon 0 Oct  9 15:47 uid_map
-r--r--r-- 1 caryon caryon 0 Oct  9 15:47 wchan

认识cwd和exe

• exe
我们可以尝试一下将进程启动然后删掉可执行程序,我们发现进程依然是可以运行的,但是一旦退出了进程就不能再运行了,但是进程结束后我们发现exe报红了,这就是由于exe存储了我们的形成该进程的原文件。

 cwd
我们之前总是听到这么一句话“当我们打开一个不存在的文件时会在当前目录下创建这个文件”,为什么会在当前目录下新建这个文件?又是怎么新建的呢?
当一个进程在实际启动的时候,该进程会用cwd记录当前工作目录,新建文件就是把cwd拿过来在后面加上/文件名,这也就意味着我们如果能改变进程启动的cwd即可对新建文件的创建位置进行控制,而系统给我们提供了这样的接口chdir()

#include<stdio.h>
#include<unistd.h>
int main()
{
   chdir("/home/caryon");
   FILE* fp=fopen("log.txt","w");
   if(fp==NULL)
   {

   }
   return 0;
}

/proc是系统为我们提供的一个访问进程信息的接口,因此实际上的ps就是对/proc进行相关的文本分析,而/proc也并不是磁盘级文件,因此频繁的创建于删除并不影响效率。

2.2 查看PPID

在Linux中,OS启动之后,新建的任何进程都是由自己的父进程创建的,我们可以使用系统调用接口getppid()获取当前进程的ppid。

#include<stdio.h>    
#include<unistd.h>    
int main()    
{    
    while(1)    
    {    
        printf("PID:%d,PPID:%d\n",getpid(),getppid());                                
        sleep(1);    
    }    
    return 0;    
}    

 

 我们反复执行了几次代码,发现该进程的PPID居然没有变,它的父进程究竟是谁啊?

[caryon@VM-24-10-centos ~]$ ps ajx|head -1;ps ajx|grep 31222
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
31222 28570 28570 31222 pts/0    28570 S+    1001   0:00 ./code
31220 31222 31222 31222 pts/0    28570 Ss    1001   0:00 -bash
 7482 31749 31748  7482 pts/1    31748 S+    1001   0:00 grep --color=auto 31222

原来是bash(Linux下的命令行解释器)。命令行中,执行命令/程序本质上就是bash创建子进程,由子进程执行我们的代码。

创建子进程
系统接口提供了fork()来创建子进程,如果创建成功则给父进程返回子进程的PID,给子进程返回0,失败则给父进程返回-1,子进程创建失败。

#include<stdio.h>
#include<unistd.h>

int gal=0;

int main()
{
    printf("我的PID是%d,我的PPID是%d\n",getpid(),getppid());
    pid_t id=fork();
    if(id==0)
    {
        while(1)
        {
            printf("我是子进程,我的PID是%d,我的PPID是%d,gal=%d\n",getpid(),getppid(),gal);
            sleep(1);
            gal++;
        }
    }

    else
    {
        while(1)
        {
            printf("我是父进程,我的PID是%d,我的PPID是%d,gal=%d\n",getpid(),getppid(),gal);
            sleep(1);
        }
    }
    return 0;
}
[caryon@VM-24-10-centos lesson11]$ ./code
我的PID是13111,我的PPID是31222
我是父进程,我的PID是13111,我的PPID是31222,gal=0
我是子进程,我的PID是13112,我的PPID是13111,gal=0
我是父进程,我的PID是13111,我的PPID是31222,gal=0
我是子进程,我的PID是13112,我的PPID是13111,gal=1
我是父进程,我的PID是13111,我的PPID是31222,gal=0
我是子进程,我的PID是13112,我的PPID是13111,gal=2
我是父进程,我的PID是13111,我的PPID是31222,gal=0
我是子进程,我的PID是13112,我的PPID是13111,gal=3
我是父进程,我的PID是13111,我的PPID是31222,gal=0
我是子进程,我的PID是13112,我的PPID是13111,gal=4

对于同一个父进程可以创建多个子进程,对于每一个子进程只能有一个父进程,故进程也是树形结构。

理解fork函数

fork()函数一旦调用之后,后面就会有两个进程了,这两个进程各自执行各自的代码,只不过fork()给父进程返回的是子进程的PID,给子进程则返回0。对于父子进程,它们的关系是代码共享,数据私有一份

为什么代码是共享的呢?

我们知道进程=内核数据结构(task_struct)+代码和数据。fork之后产生子进程,子进程的内核数据结构拷贝自父进程,但是没有代码和数据怎么办呢?系统就会让子进程的内核数据结构指向父进程的代码和数据。

为什么数据是私有一份的呢?

上面我们不是说子进程的内核数据结构指向父进程的代码和数据吗?这是因为进程具有很强的独立性,多个进程运行时互不干扰(即使是父子)。代码是只读的,可以共享;但是数据不一定时只读的,当数据被修改时两个进程必须要进行分割,也就是各自要私有一份。

#include<stdio.h>
#include<unistd.h>

int gal=0;
int main()
{
    pid_t id=fork();
    if(id==0)
    {
        while(1)
        {
        printf("I am a subprocess,my pid is %d,my ppid is %d,gal = %d\n",getpid(),getppid(),gal++);
        sleep(1);
        }
    }
    else
    {
        while(1)
        {
        printf("I am a parentprocess,my pid is %d,my ppid is %d,gal = %d\n",getpid(),getppid(),gal);
        sleep(1);
        }
    }
    return 0;
}

上面的这段代码我们可以看到全局变量gal在两个进程中的数值是不相同的,也就是进程间实现了数据私有一份的。

这时我们也能理解了fork的返回值了。因为id也是变量,返回的本质就是向指定变量进行写入,因此两个进程的id是不相同的。