进程的生老病死
进程状态
说进程是动态的活动的实体,指的是进程会有很多种运行状态,一会儿睡眠、一会儿暂停、一会儿又继续执行。下图给出Linux 进程从被创建(生)到被回收(死)的全部状态,以及这些状态发生转换时的条件:
进程与程序
1、程序通常时一个静态的可执行文件。
2、进程是程序运行动态体现。
3、在windos下可执行文件类型是exe; 通常在Linux下可执行文件类型是ELF,Linux中可执行文件没有固定后缀。
4、进程是系统管理的最小单位。
ELF格式文件类型
1、可执行文件
2、可重定位文件:.o .a (静态库)
3、共享目标文件:.so (动态链接库)
4、核心转储文件:.dump
// 查看可执行文件a.out的头部信息
readelf -h a.out
struct task_struct结构体位置:
/usr/src/linux-headers-4.15.0-142-generic/include/linux/sched.h
查看进程的命令
pstree // 查看系统进程树
ps -ef // 查看系统中当前进程
top - H // 动态查看系统中当前进程
htop // 动态查看系统中当前进程(需要自行安装插件)
// sudo apt-get install htop
init属于整个系统中最顶层的进行(没有父进程)
其余进程都有父进程
创建子进程——fork()
- 功能:创建一个新的进程
- 头文件:#include <unistd.h>
- 原型:pid_t fork(void);
- 返回值
- 成功0或者大于0的正整数
- 失败-1
- 注:该函数执行成功之后,将会产生一个新的子进程,在新的子进程中其返回值为 0,在原来的父进程中其返回值为大于0的正整数,该正整数就
是子进程的 PID
要着重注意的几点:
fork( )会使得进程本身被复制(想想细胞分裂),因此被创建出来的子进程和父进程几乎是一模一样的,说“几乎”意味着子进程并不是 100%为一份父进程的复印件,他们的具体关系如下:
父子进程的以下属性在创建之初完全一样,子进程相当于搞了一份复制品:
-
实际 UID 和 GID,以及有效 UID 和 GID。
-
所有环境变量。
-
进程组 ID 和会话 ID。
-
当前工作路径。除非用 chdir()加以修改
-
打开的文件。
-
信号响应函数。
-
整个内存空间,包括栈、堆、数据段、代码段、标准 I0 的缓冲区等等。
而以下属性,父子进程是不一样的:
- 进程号 PID。PID 是身份证号码,哪怕亲如父子,也要区分开
- 记录锁。父进程对某文件加了把锁,子进程不会继承这把锁。
- 挂起的信号。这些信号是所谓的“悬而未决”的信号,等待着进程的响应,子进程也不会继承这些信号。
子进程会从 fork( )返回值后的下一条逻辑语句开始运行。这样就避免了不断调用fork()而产生无限子孙的悖论。
父子进程是相互平等的:他们的执行次序是随机的,或者说他们是并发运行的,除非使用特殊机制来同步他们,否则你不能判断他们的运行究竟谁先谁后。
父子进程是相互独立的:由于子进程完整地复制了父进程的内存空间,因此从内存间的角度看他们是相互独立、互不影响的。
例:
int main(int argc,char *argv[])
{
//1、 num 在父子进程中对应的虚拟内存地址相同,
// 但不是同一空间,因此在父进程中修改num,不会影响子进程中num的值
//int num = 10;
// 2、堆空间和数据也会被子进程复制,但实际空间相互独立,操作互不影响
//int* ptr = (int*)malloc(4);
//*ptr = 11;
// 3、文件操作
// 子进程会赋值当前的文件描述符,当父进程读取一段数据之后,子进程再进行读取时,文件指针已经偏移。
// 原因:num、ptr堆栈数据的存在与否会受当前程序影响,而文件的存在通常不受当前程序影响。
int fd = open("head.h", O_RDWR);
pid_t pid = fork();
if (pid < 0)
{
perror("fork fail");
return -1;
}
if (pid == 0) // 子进程
{
//1、
//sleep(1);
//printf("I'm Son! &num = %p\n", &num);
//2、
//sleep(1);
//printf("son ptr=%p, *ptr=%d\n", ptr, *ptr);
//3、
printf("\nson fd = %d\n", fd);
char buf[100];
bzero(buf, sizeof(buf));
read(fd, buf, 80);
puts(buf);
}
if (pid > 0) // 父进程
{
//1、
//num++;
//printf("I'm Parent! &num = %p\n", &num);
//2、
//*ptr = 100;
//printf("parent ptr=%p, *ptr=%d\n", ptr, *ptr);
//3、
printf("\nparent fd = %d\n", fd);
char buf[100];
bzero(buf, sizeof(buf));
read(fd, buf, 80);
puts(buf);
}
pause();
return 0;
}
进程中加载新的程序文件——execl()
-
功能:在进程中加载新的程序文件或者脚本,覆盖原有代码,重新运行
-
头文件:#include <unistd.h>
-
原型:int execl(const char *path, const char *arg, …);
-
参数:
- file:即将被加载执行的 ELF 文件或脚本的名字
- arg:以列表方式罗列的 ELF 文件或脚本的参数
- argv:以数组方式组织的 ELF文件或脚本的参数
- envp:用户自定义的环境变量数组
-
返回值
- 成功:不返回
- 失败:-1
注:
- 被加载的文件的参数列表必须以自身名字为开始,以 NULL为结尾。比如要加载执行当前目录下的一个叫做 a.out 的文件,需要一个参数"abcd",那么正确的调用应该是:
execl(“./a.out”, “a.out",“abcd", NULL),
或者:
const char *argv[3]= {“a.out", “abcd”, NULL};
execv(“./a.out”, argv); - exec函数簇成功执行后,原有的程序代码都将被指定的文件或脚本覆盖,因此这
些函数一旦成功后面的代码是无法执行的,他们也是无法返回的。
例:
int main()
{
// 创建一个子进程
pid_t pid = fork();
if (pid == 0)
{
// 子进程
//execl("/bin/ls", "ls", "-l", NULL);
printf("我还在!\n"); // 无法被执行
sleep(5);
printf("我走了!\n"); // 无法被执行
}
if (pid > 0)
{
父进程
//while(1)
//{
// puts("hello");
// sleep(1);
//}
}
return 0;
}
标签:状态,文件,pid,num,Linux,进程,buf,ptr
From: https://blog.csdn.net/qixi_ao/article/details/142220513