Linux C 程序 进程控制(17)
进程控制
1.进程概述
现代操作系统的特点在于程序的并行执行。
Linux是一个多用户多任务的操作系统。
ps .pstree 查看进程
进程除了进程id外还有一些其他标识信息,可以通过相应的函数获得。// 这些函数在unistd.h里声明。
2.Linux进程的结构
Linux一个进程由3部分组成:代码段,数据段,堆栈段。
代码段存放可执行代码
数据段存放程序的全局变量,常量,静态变量
堆栈段存放动态分配的内存变量,堆栈中的栈用于函数调用,存放着函数的参数,函数内部定义的局部变量。
3.Linux进程的状态
1.运行状态R runnable
2.可中断等待状态S sleeping
3.不可中断等待状态D uninterruptible sleep
4.僵死状态Z zombile
5.停止状态T traced or stoped
ps axo stat,euid,ruid,tty,tpgid,sess,pgrp,ppid,pid,pcpu,comm
列stat
后缀的意义:
<高优先级进程
N低优先级进程
L内存锁页,即页不可以被换出内存
s 该进程为会话首进程
l 多线程进程
+ 进程位于前台进程组
eg:Ssl 表示 该进程处于可中断等待状态,为会话首进程,而且是一个多线程进程。
ps axo stat,pid
4.进程控制
Linux进程控制包括创建进程,执行新程序,退出进程,以及改变进程优先级等。
Linux提供进程控制的系统调用:
fork:创建一个进程
exit:终止进程
exec: 执行一个应用程序
wait:将父进程挂起,等待子进程终止
getpid:获取当前进程的进程id
nice:改变进程的优先级
5.进程的内存映像
1.Linux程序转化成进程
linux的C程序生成分为4阶段:预编译,编译,汇编,链接。
编译器gcc经过预编译,编译,汇编将源程序文件转化成目标文件。
若程序中有多个目标文件或者程序中使用了库函数,
编译器还要将所有的目标文件或所需的库链接起来,最后生成可执行程序。
当程序执行时:操作系统将可执行程序复制到内存中,程序转化成进程要经过以下几个步骤:
1.内核将程序读入内存,为程序分配内存空间。
2.内核为该进程分配进程标识符(PID)和其他所需资源
3.内核为该进程保存PID及相应的状态,把进程放入运行队列中等待执行。 程序转化成进城后就可以被操作系统的调度程序执行了。
2.进程的内存映像
进程在内存中如何存放可执行程序文件,在将程序转化成进程时,操作系统把可执行程序由硬盘复制到内存中。
Linux下程序映像的一般布局:
内存的低地址到高地址依次如下:
1.代码段:即二进制机器代码,只读,可被多个进程共享,如一个进程创建了一个子进程,父子进程共享代码段,此外子进程还获得父进程数据段,堆,栈的复制。
2.数据段:存储已被初始化的变量,包括全局变量和已经被初始化的静态变量
3.未被初始化的数据段:存储未被初始化的静态变量,也被称为bss段
4.堆:用于存放程序运行中动态分配的变量
5.栈:用于函数的调用,保存函数的返回地址,函数参数,函数内部的局部变量
高地址还存储了命令行参数和环境变量
可执行程序和内存映像的区别:
可执行程序位于磁盘,内存映像在内存中。
可执行程序没有堆栈,程序加载到内存才有堆栈
可执行程序虽然也有未被初始化的数据段,但他并不被存储在位于硬盘的可执行文件中。
可执行程序是静态不变的。内存映像随着程序的运行变化的。
6.进程操作
#include<stdio.h> #include<sys/types.h> #include<unistd.h> #include<stdlib.h> int main(void){ pid_t pid; printf("process create begin ......\n"); pid = fork(); switch(pid){ : printf("child process id is %d , parent process is %d\n", pid , getppid()); break; : printf("process create failed!\n"); break; default: printf("child process id is %d , parent process is %d\n",pid , getppid()); break; } exit(); }
fork之后,父进程执行还是子进程先执行先执行是不确定的,取决于内核的调度算法。
操作系统一般让所有的进程拥有同等执行权利。
除非某进程的优先级比其他进程高。
fork创建进程失败时,返回-1,失败的原因:1.父进程拥有子进程的数量超过限制,2.可供使用的内存不足。
子进程会继承父进程的很多属性:用户id,组id,当前工作目录,根目录,打开的文件,创建文件时使用的屏蔽字,信号屏蔽字,上下文环境,共享的存储段,资源限制。
子进程与父进程不同的属性:子进程有自己的进程id,fork返回值不同,父进程返回子进程的id,子进程的则为0,不同的父进程id,子进程的父进程id为创建他的父进程id
子进程共享父进程打开的文件描述符,但父进程对文件描述符的改变不会影响子进程中的文件描述符。子进程不继承父进程设置的文件锁,
子进程不继承父进程设置的警告,子进程的未决信号集被清空
#include<stdio.h> #include<sys/types.h> #include<unistd.h> int main(){ pid_t pid ; char *msg; int k; printf("process creat!\n"); pid = fork(); switch(pid){ : msg = "child process is running\n"; k = ; break; : msg = "proicess failed \n"; break; default: msg ="parent process is running\n"; k = ; break; } ){ puts(msg); sleep(); k--; } exit(); } output: [fubin@localhost C]$ ./process_2 process creat! parent process is running child process is running parent process is running child process is running parent process is running child process is running parent process is running parent process is running
孤儿进程:如果一个子进程的父进程先于子进程结束,子进程就成了一个孤儿进程,它由init收养,成为init的子进程。
#include<stdio.h> #include<sys/types.h> #include<unistd.h> int main(){ pid_t pid ; pid = fork(); switch(pid){ : ){ printf("a bankground process ,PID :%d\n ,parentID %d\n",getpid(),getppid()); sleep(); } : perror("process failed!"); exit(-); default: printf("i am a parent process ,my pid is %d\n",getpid()); exit(); } ; } output: [fubin@localhost C]$ ./process_3 i am a parent process ,my pid [fubin@localhost C]$ a bankground process ,PID : ,parentID a bankground process ,PID : ,parentID a bankground process ,PID : ,parentID ......
先执行父进程,为2561,再执行子进程,父进程此时已经结束,显示子进程2562的父进程pid=1,为init进程
vfork函数:
1.和fork一样,调用一次,返回两次。
2.使用fork创建一个子进程时,子进程完全复制父进程的资源,这样得到的子进程完全独立于父进程,有良好的并发性。
使用vfork创建子进程时,不会将父进程的地址空间完全复制给子进程。而是共享父进程的地址空间,
子进程完全运行在父进程的地址空间上。子进程对该地址的修改父进程完全可见
3.fork创建子进程时,哪个先执行取决于系统的调度算法,而vfork保证子进程先执行,当他调用exec或者exit后,父进程才可能被调度执行。
如果调用exec或exit之前子进程依赖父进程的某个行为,会导致死锁。
fork:完全复制父进程资源,系统开销大,如果父进程创建之后就马上创建子进程,而父进程不需要了,就没有必要用fork复制资源。
#include<stdio.h> #include<sys/types.h> #include<unistd.h> ; int main(){ pid_t pid ; , i ; printf("fork is diff with vfork!\n"); pid = fork(); //pid = vfork(); switch(pid){ : i = ; //判断i是否大于0,然后自减 ){ printf("child process is running!\n"); globVar++; var++; sleep(); } printf("child 's globVar = %d , var = %d \n",globVar , var); break; : printf("process creat failed!\n"); exit(); default: i = ; ){ printf("parent process is running\n"); globVar++; var++; sleep(); } printf("parent 's globVar = %d , var = %d \n",globVar,var); exit(); } ; } fork output: fork is diff with vfork! parent process is running child process is running! parent process is running child process is running! parent process is running child process is running! parent process is running child 's globVar = 8 , var = 4 parent process is running parent 's globVar = 10 , var = 6 两个进程中的globVar和var独立 vfork output: fork is diff with vfork! child process is running! child process is running! child process is running! child 's globVar = 8 , var = 4 parent process is running parent process is running parent process is running parent process is running parent process is running parent 's globVar = 13 , var = 5 ------------------------------ fork is diff with vfork! child process is running! globVar=, child process is running! globVar=, child process is running! globVar=, child 's globVar = 8 , var = 4 parent process is running globVar=, parent process is running globVar=, parent process is running globVar=, parent process is running globVar=, parent process is running globVar=, parent 's globVar = 13 , var = 5
父进程的var从0开始?
创建守护进程:daemon
后台运行,没有控制终端与之相连。独立于控制终端,通常周期性的执行某任务。
Linux大多数服务器就是用守护进程方式实现的。
如:Internet服务器进程inetd,Web服务器进程http。守护进程在后台执行,相当于windows的系统服务。
守护进程的启动方式:
1.Linux系统启动时,从启动脚本/etc/rc.d中启动
2.可以由作业规划进程crond 启动
3.用户终端执行(通常是shell)
创建守护进程有如下要点:
1.让进程后台执行。方法是用fork产生一个子进程,然后父进程退出
2.调用setsid创建一个新的会话。控制终端,登录会话,进程组通常从父进程继承下来,守护进程要摆脱他们,不受他们影响,
方法是调用setsid是进程成为一个会话组长。
当进程本来就是会话组长时,setsid会调用失败,但第一点保证了不是会话组长,调用成功后,进程成为了新的会话组长和进程组长,并与原来的登录会话和进程组相脱离。
3.禁止进程重新打开终端。1,2已经使进程成为了一个无终端的会话组长,但是可以重新打开一个终端。可以通过使进程不再是会话组长来实现。再一次调用fork创建新子进程,使父进程退出。
4.关闭不再需要的文件描述符。
5.将当前目录更改为艮目录
6.将文件创建时使用的屏蔽字设置为0
7.处理SIGCHLD信号。
eg: ...
进程退出:
正常退出
1,main函数执行return
2,调用exit函数
3,调用_exit函数
异常退出:
1,调用abort函数
2,进程收到某个信号,该信号使进程终止。
不管使用哪种方式,都会执行内核中的同一段代码。这段代码用来关闭进程打开的文件描述符,释放它所占用的内存和其他资源。
退出方式比较:
exit与return。exit把控制权交给系统,return交给函数
exit与abort:exit正常终止,abort异常终止
exit(int exit_code),0为正常终止,其他是异常终止
exit()与_exit(),exit()在stdlib.h声明,_exit()在unistd.h声明。
_exit();执行立即返回给内核,exit()会先执行一段清除操作。
父子进程终止的先后顺序不同会产生不同结果:
子进程退出前父进程先退出,子进程交给init进程接管。
子进程先于父进程终止,父进程没有调用wait等待子进程结束,子进程进入僵死状态,并且会一直保持下去除非重启系统。
子进程处于僵死状态时,内核只保存该进程的一些必要信息以备父进程所需。此时子进程始终占用着资源,同时也减少了系统可以创建的最大进程数。
子进程先于父进程终止,且父进程调用了wait或waitpid函数,则父进程会等待子进程的结束。
执行新程序:
使用fork或者vfork创建子进程后,子进程通常会调用exec来执行另一个程序。系统调用exec用于执行一个可执行程序以替代当前进程的执行映像。
exec并没有生成新进程,一个进程一旦调用了exec,它本身就死亡了,系统把代码段替换成新的程序代码。废弃原有的数据段和堆栈段,唯一保留的是进程id。
linux下的exec有六种调用方式:声明在头文件unistd.h中
#include <unistd.h> extern char **environ; 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[]);
为了更好的使用exec族,用到了环境变量的概念
环境变量:为了使用户灵活使用shell,环境变量包括用户主目录,终端类型,当前目录,他们定义在用户的工作环境里,用env查看环境变量的值。
获得环境变量:
eg:......
执行新程序后除了保持进程id,父进程id,实际用户id,实际组id外:还有:
当前工作目录,根目录,创建文件时使用的屏蔽字,进程信号屏蔽字,未决警告,和进程相关的使用处理器的时间,控制终端,文件锁。
等待进程结束:
wait 等待第一个终止的子进程
waitpid 等待特定pid终止的子进程
进程的其他操作:
1.获得进程id getpid()
2.设置实际用户id和有效用户id setuid ,设置实际组和有效组Id,setgid
su命令通过这个函数实现
改变进程的优先级
getpriority() 返回一组进程的优先级
setpriority()
nice()函数
实现自己的myshell-------
Linux C 程序 进程控制(17)的更多相关文章
- linux 命令及进程控制
main.c main.o/main.obj main/main.exe 编译 连接 程序运行; 两步: gcc/g++ -c mai ...
- linux与Windows进程控制
进程管理控制 这里实现的是一个自定义timer用于统计子进程运行的时间.使用方式主要是 timer [-t seconds] command arguments 例如要统计ls的运行时间可以直接输入t ...
- linux系统调用之进程控制
1 进程控制: fork 创建一 ...
- Linux嵌入式 -- 内核 - 进程控制 和 调度
1. 进程四要素 1. 有一段程序供其执行.这段程序不一定是某个进程所专有,可以与其他进程共用. 2. 有进程专用的内核空间堆栈. 3. 在内核中有一个task_struct数据结构,即通常所说的&q ...
- 《嵌入式linux应用程序开发标准教程》笔记——7.进程控制开发
进程是系统资源的最小单元,很重要. 7.1 linux进程的基本概念 定义:一个程序的一次执行过程,同时也是资源分配的最小单元.程序是静态的,而进程是动态的. 进程控制块:linux系统用进程控制块描 ...
- 【linux草鞋应用编程系列】_2_ 环境变量和进程控制
一. 环境变量 应用程序在执行的时候,可能需要获取系统的环境变量,从而执行一些相应的操作. 在linux中有两种方法获取环境变量,分述如下. 1.通过main函数的参数获取环境变量 ...
- 【Linux程序设计】之进程控制&守护进程
这个系列的博客贴的都是我大二的时候学习Linux系统高级编程时的一些实验程序,都挺简单的. 实验题目:Linux环境下的进程控制 实验目的:熟悉并掌握Linux环境下进程的相关函数的应用:守护进程的概 ...
- linux进程及进程控制
Linux进程控制 程序是一组可执行的静态指令集,而进程(process)是一个执行中的程序实例.利用分时技术,在Linux操作系统上同时可以运行多个进程.分时技术的基本原理是把CPU的运行时间划 ...
- Linux进程控制(二)
1. 进程的创建 Linux下有四类创建子进程的函数:system(),fork(),exec*(),popen() 1.1. system函数 原型: #include <stdlib.h&g ...
随机推荐
- MySQL数据库还原:路径必须用正斜杠?
也是术业不精,其实之前也用命令行还原过几次MySQL数据库,但总记不清语法.这不,今天想把另一台电脑上备份的数据库还原过来,结果不停报错,如下图所示: 后来才发现,因为偷懒直接复制的路径名里,用的全是 ...
- Xutils3的使用
Xutils是前两年很火的一个三方库(githup地址),是一个工具类,分为4个模块:DbUtils.HttpUtils.ViewUtils. BitmapUtils,还有一个非常使用功能就是LogU ...
- xUtils的介绍
鉴于大家的热情,我写了一篇Android 最火框架XUtils之注解机制详解<-点击查看 xUtils简介 xUtils 包含了很多实用的android工具. xUtils 最初源于Afinal ...
- 深入理解HTTPS通讯原理
一.HTTPS简介 HTTPS(Hyper Text Transfer Protocol over Secure Socket Layer),简单来讲就是加了安全的HTTP,即HTTP+SSL:我们知 ...
- Linux文件系统的barrier:启用还是禁用
大多数当前流行的Linux文件系统,包括EXT3和EXT4,都将文件系统barrier作为一个增强的安全特性.它保护数据不被写入日记.但 是,在许多情况下,我们并不清楚这些barrier是否有用.本文 ...
- Http error code
概要 当用户试图通过HTTP或文件传输协议(FTP)访问一台正在运行Internet信息服务(IIS)的服务器上的内容时,IIS返回一个表示该请求的状态的数字代码.该状态代码记录在IIS日志中,同时也 ...
- oracle 常用技巧及脚本
[Q]怎么样查询特殊字符,如通配符%与_ [A]select * from table where name like 'A/_%' escape '/' [Q]如何插入单引号到数据库表中 [A]可以 ...
- canvas基础2--绘制图形
栅格 绘制矩形 不同于SVG,HTML中的元素canvas只支持一种原生的图形绘制:矩形.所有其他的图形的绘制都至少需要生成一条路径.不过,我们拥有众多路径生成的方法让复杂图形的绘制成为了可能. 首先 ...
- 布料解算插件 Qualoth 重点参数分享
前言 Qualoth是韩国FXGear公司推出的一款布料模拟插件,可以计算出很自然的衣褶以及动态效果,并且能应对大幅度动作的碰撞解算,可以和Houdini的Cloth Solver相媲美: 目前这款插 ...
- IOS小知识纪录
1.scrollView缩放 #import "ViewController.h" @interface ViewController () <UIScrollViewDel ...