中嵌之进程控制

来源:互联网 发布:微信红包埋雷人工算法 编辑:程序博客网 时间:2024/05/01 22:55

Linux进程控制

1、程序和进程
1.1 程序

程序(program)是存放在磁盘文件中的可执行文件。程序的执行实例被称为进程(process)。本书的每一页几乎都会使用“进程”这一术语。某些操作系统用任务表示正被执行的程序。

进程控制块,进程是linux系统的基本调度单元,一般通过进程控制块来描述进程变化,进程控制块包含进程的描述信息,控制信息以及资源信息,是进程的一个静态描述。在 Linux 中,进程控制块中的每一项都是一个 task_struct结构,它是在 include/linux/sched.h 中定义的。

1.2 进程的标示符——进程和进程ID
每个linux进程都一定有一个唯一的数字标识符,称为进程ID(process ID)。进程ID总是一非负整数。
1.3 linux下的进程结构
      Linux系统是一个多进程的系统,进程之间具有并行性、互不干扰的特点。
      linux中进程包含3个段,分别为“代码段”、“数据段”和“堆栈段”。

    “数据段”存放全局变量、常数以及动态数据分配的空间(malloc函数取得的空间);

    “代码段”存放程序代码;
    “堆栈段”存放子程序的返回地址、子程序的参数以及程序的局部变量。


1.3 init进程
进程ID为1通常是init进程,在自举过程结束时由内核调用。
init进程绝不会终止。
它是一个普通的用户进程(与交换进程不同,它不是内核中的系统进程),但是它以超级用户特权运行。
1.4 获取进程标识
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void); 返回:调用进程的进程I D
pid_t getppid(void); 返回:调用进程的父进程I D
uid_t getuid(void); 返回:调用进程的实际用户I D
uid_t geteuid(void); 返回:调用进程的有效用户I D
gid_t getgid(void); 返回:调用进程的实际组I D
gid_t getegid(void); 返回:调用进程的有效组I D
1.5 fork函数
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
返回:子进程中为0,父进程中为子进程I D,出错为-1

1.6 进程创建

    由fork创建的新进程被称为子进程( child process)。

    该函数被调用一次,但返回两次。两次返回的区别是子进程的返回值是0,而父进程的返回值则是子进程的进程ID。
     一般来说,在f o r k之后是父进程先执行还是子进程先执行是不确定的。这取决于内核所使用的调度算法。
     使用fork函数得到的子进程是父进程的处继承了整个进程的地址空间,包括:进程上下文、进程堆栈、内存信息、打开的文件描述符、信号控制设置、进程优先级、进程组号、当前工作目录、根目录、资源限制、控制终端等。而子进程所独有的只有它的进程号、资源使用和计时器等。
     父、子进程之间的区别是:
fork的返回值; 进程I D、不同的父进程I D;子进程的t m s _ u t i m e , t m s _ s t i m e , t m s _ c u t i m e以及t m s _ u s t i m e设置为0;父进程设置的锁,子进程不继承;子进程的未决告警被清除;子进程的未决信号集设置为空集。
Fork 例如:fork.c
1.7 vfork函数
    vfork函数的调用序列和返回值与fork相同,但两者的语义不同。

    现在很多的实现并不做一个父进程数据段和堆的完全拷贝,因为在f o r k之后经常跟随着exec。作为替代,使用了在写时复制( copy-on-Write, COW)的技术。这些区域由父、子进程共享,而且内核将它们的存取许可权改变为只读的。如果有进程试图修改这些区域,则内核为有关部分,典型的是虚存系统中的“页”,做一个拷贝。如:uclinux中的进程创建。

进程创建-vfork
#include <sys/types.h>
#include <unistd.h>
pid_t vfork(void)
功能:创建子进程。

fork和vfork区别:

1. fork:子进程拷贝父进程的数据段
vfork:子进程与父进程共享数据段
2. fork:父、子进程的执行次序不确定
vfork:子进程先运行,父进程后运行

#include <unistd.h>
#include <stdio.h>
int main(void)
{
pid_t pid;
int count=0;
pid = vfork();
count++;
printf( “count = %d\n", count );
return 0;


1.8  exec函数族

在用f o r k函数创建子进程后,子进程往往要调用一种e x e c函数以执行另一个程序。
当进程调用一种e x e c函数时,该进程完全由新程序代换,而新程序则从其m a i n函数开始执行。因为调用e x e c并不创建新进程,所以前后的进程I D并
未改变。e x e c只是用另一个新程序替换了当前进程的正文、数据、堆和栈段。

#include <unistd.h>
int execl(const char * pathname, const char * arg 0, ... /* (char *) 0 */);
int execv(const char * pathname, char *const a rgv [] );
int execle(const char * pathname, const char * a rg 0, .../* (char *)0, char *const e n v p [] */);
int execve(const char * pathname char *const a rgv [], char *const envp [] );
int execlp(const char * pathname, const char * a rg 0, ... /* (char *) 0 */);
int execvp(const char * pathname, char *const a rgv [] );
六个函数返回:若出错则为- 1,若成功则不返回

参数表的传递有关( l表示表( list )——表示逐个列举的方式,v表示矢量( vector ) ——表示将所有参数整体构造指针数组传递);
e:可传递新进程环境变量,execle、execve,就可以在 envp[]中指定当前进程所使用的环境变量。exec 函数族可以默认系统的环境变量,也可以传入指定的环境变量(e)。
p:可执行文件查找方式为文件名,execlp、execvp;

例如:execlp.c, execl.c, execle.c, execve.c


fork和exec的区别:
fork创建一个新的进程,产生一个新的PID。
exec启动一个新程序,替换原有的进程,因此进程的PID不会改变。

1.9 exit和_exit
exit和_exit用于中止进程;
_exit的作用:直接使进程停止运行,清除其使用的内存空间,并清除其在内核中的数据结构;
exit与_exit函数不同,exit函数在调用exit系统之前要检查文件打开情况把文件缓冲区的内容写回文件中去。如调用printf()函数。

1.10 wait和waitpid函数
当一个进程正常或异常终止时,内核就向其父进程发送SIGCHLD信号。因为子进程终止是个异步事件(这可以在父进程运行的任何时候发生),所以这种信号也是内核向父进程发的异步通知。父进程可以忽略该信号,或者提供一个该信号发生时即被调用执行的函数(信号处理程序)。对于这种信号的系统默认动作是忽略它。
wait函数用于使父进程阻塞,直到一个子进程结束或者该进程接收到一个指定信号为止。

调用wait或waitpid的进程可能会:
阻塞(如果其所有子进程都还在运行)。
带子进程的终止状态立即返回(如果一个子进程已终止,正等待父进程存取其终止状态)。
出错立即返回(如果它没有任何子进程)。

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int * status) ;
pid_t waitpid(pid_t pid, int * status, int options) ;
两个函数返回:若成功则为子进程I D号,若出错则为-1. 
Status选项,为空时,代表任意状态结束的子进程,若不为空,则代表指定状态结束的子进程。

wait和waitpid函数的区别:
在一个子进程终止前, wait 使其调用者阻塞,而waitpid 有一选择项,可使调用者不阻塞。
waitpid并不等待第一个终止的子进程—它有若干个选择项,可以控制它所等待的特定进程。
实际上wait函数是waitpid函数的一个特例。

对于waitpid的p i d参数的解释与其值有关:
pid == -1 等待任一子进程。于是在这一功能方面waitpid与wait等效。
pid > 0 等待其进程I D与p i d相等的子进程。
pid == 0 等待其组I D等于调用进程的组I D的任一子进程。
pid < -1 等待其组I D等于p i d的绝对值的任一子进程。


waitpid函数提供了wait函数没有提供的三个功能:
(1) waitpid等待一个特定的进程(而w a i t则返回任一终止子进程的状态)。 
(2) waitpid提供了一个w a i t的非阻塞版本。有时希望取得一个子进程的状态,但不想阻塞。
(3) waitpid支持作业控制(以WUNTRACED选择项)。

例如:waitpid.c

例:wait.c (演示)

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
void main()
{
pid_t pc,pr;
pc=fork();
if (pc==0){ /* 如果是子进程 */
printf(“This is child process with pid of %d\n”,getpid());
sleep(10); /* 睡眠10秒钟 */
}
else if (pc>0){ /* 如果是父进程 */
pr=wait(NULL); /* 等待 */
printf("I catched a child process with pid of %d\n"),pr);
}
exit(0);


2. 守护进程
2.1 概述
守护进程( daemon)是生存期长的一种进程,是Linux 中的后台服务进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。它们常常在系统引导装入时起动,在系统关闭时终止。因为它们没有控制终端,所以说它们是在后台运行的。linux系统有很多守护进程,它们执行日常事物活动。大多数服务都是通过守护进程实现的,如本书在第二章中讲到的系统服务都是守护进程。同时,守护进程还能完成许多系统任务,例如,作业规划进程crond、打印进程lqd等(这里的结尾字母d就是Daemon的意思)。
由于在Linux 中,每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端就称为这些进程的控制终端,当控制终端被关闭时,相应的进程都会自动关闭。但是守护进程却能够突破这种限制,它从被执行开始运转,直到整个系统关闭时才会退出。如果想让某个进程不因为用户或终端或其他的变化而受到影响,那么就必须把这个进程变成一个守护进程。可见,守护进程是非常重要的。(摘自《嵌入式Linux应用程序开发详解》)


2.2  守护进程特征
所有守护进程都以超级用户(用户I D为0)的优先权运行。
没有一个守护进程具有控制终端—终端名称设置为问号(?)、终端前台进程组I D设置为-1。缺少控制终端可能是精灵进程调用了s e t s i d的结果。
除u p d a t e以外的所有精灵进程都是进程组的首进程,对话期的首进程,而且是这些进程组和对话期中的唯一进程。u p d a t e是它所在进程组和对话期(中的唯一进程,但是该进程组的首进程(可能也是该对话期的首进程)已经终止。
所有这些守护进程的父进程都是i n i t进程。
2.3 守护进程编程规则(5步)
(1)创建子进程,父进程退出: 
         首先做的是调用fork,然后使父进程e x i t。这样做实现了下面几点:第一,如果该守护进程是由一条简单s h e l l命令起动的,那么使父进程终止使得s h e l l认为这条命令已经执行完成。第二,子进程继承了父进程的进程组I D,但具有一个新的进程I D,这就保证了子进程不是一个进程组的首进程。这对于下面就要做的setsid调用是必要的前提条件。

   由于父进程已经先于子进程退出,会造成子进程没有父进程,从而变成一个孤儿进程。在Linux 中,每当系统发现一个孤儿进程,就会自动由1 号进程(也就是init 进程)收养它,这样,原先的子进程就会变成init进程的子进程了。(摘自《嵌入式Linux应用程序开发详解》)
(2)调用setsid以创建一个新的会话,并担任该会话组的组长。调用setsid 作用有三个:
    (a)成为新对话期的首进程,
    (b)成为一个新进程组的首进程,
    (c)脱离控制终端。
(会话组是一个或多个进程组的集合)


setsid()函数格式:
#include <sys/types.h>
#include <unist.h>
Pid_t setsid(void)
函数成功时返回该进程组ID, 出错时返回-1
概念:进程组、组长进程、会话期
进程组
  • 进程组是一个或多个进程的集合。进程组由进程组ID 来惟一标识。除了进程号(PID)之外,进程组ID 也一个进程的必备属性。
        每个进程组都有一个组长进程,其组长进程的进程号等于进程组ID。且该进程ID 不会因组长进程的退出而受到影响。
  •  会话期

      会话组是一个或多个进程组的集合。通常,一个会话开始于用户登录,终止于用户退出,在此期间该用户运行的所有进程都属于 这个会话期,它们之间的关系如下图 所示。


(摘自《嵌入式Linux应用程序开发详解》)

(3)改变当前目录为根目录
chdir(“/”);
        从父进程继承过来的当前工作目录可能在一个mnt的文件系统中。因为守护进程通常在系统再引导之前是一直存在的,所以如果守护进程的当前工作目录在一个mnt文件系统中,那么该文件系统就不能被拆卸。
       通常的做法是让“/”作为守护进程的当前工作目录,也可以把当前工作目录换成其他的路径,如/tmp。
(4)重设文件权限掩码
        umask(0);
由继承得来的文件方式创建屏蔽字可能会拒绝设置某些许可权。例如,若守护进程要创建一个组可读、写的文件,而继承的文件方式创建屏蔽字,屏蔽了这两种许可权,则所要求的组可读、写就不能起作用。


(5) 关闭不再需要的文件描述符。
用fork函数创建的子程序会从父进程那继承一些已经打开的文件,由此为使守护进程就不再持有从其父进程继承来的某些文件描述符。但是,究竟关闭哪些描述符则与具体的精灵进程有关,可以程序中的方法关闭所有文件描述符。
for (i=0;i<MAXFILE;I++)
close(i);
守护例如:dameon.c
3. 守护进程的出错处理
由于守护进程完全脱离了控制终端,因此,不能像其他程序一样通过输出错误信息到控制台的方式来通知程序员。
通常的办法是使用syslog服务,将出错信息输入到“/var/log/message”系统日志文件中去。
Syslog是linux中的系统日志管理服务通过守护进程syslog来维护。
3.1 syslog函数说明
Openlog函数用于打开系统日志服务的一个连接;
Syslog函数用于向日志文件中写入消息,在这里可以规定消息的优先级、消息的输出格式等;
Closelog函数用于关闭系统日志服务的连接。
3.1.1 syslog函数格式
(1)openlog函数
#include <syslog.h>
void openlog(char * ident, int option, int facility) ;
Ident:要向每个消息加入的字符串,通常为程序的名称;


option参数:
LOG_CONS: 若日志消息不能通过发送至syslogd ,则将该消息写至控制台;
LOG_NDELAY: 立即打开UNIX域数据报套接口至syslsgd守护进程。通常,在记录第一条消息之前,该套接口不打开。
LOG_PERROR:除将日志消息发送给syslog外,还将它写至标准出错(stderr)。
LOG_PID:每条消息都包含进程ID,此选择项可供对每个请求都fork一个子进程的守护进程使用。
openlog的facility参数
LOG_AUTH 授权程序: login.su,getty, ⋯
LOG_CRONcron 和 at
LOG_DAEMON 系统守护进程:ftpd,routed, ⋯
LOG_KERN 内核产生的消息
LOG_LOCAL0~7  保留由本地使用
LOG_LPR 行打系统:lpd, lpc, ⋯
LOG_MAIL 邮件系统
LOG_NEWSU senet网络新闻系统
LOG_SYSLOG syslogd守护进程本身
LOG_USER 来自其他用户进程的消息
LOG_UUCP UUCP系统


(2)syslog函数
#include <syslog.h>
void syslog(int priority, char *format, ...);
Priority选项(消息优先级)
LOG_EMERG 紧急(系统不可使用) (最高优先级)
LOG_ALERT 必须立即修复的条件
LOG_CRIT 临界条件(例如,硬设备出错)
LOG_ERR 出错条件
LOG_WARNING 警告条件
LOG_NOTICE 正常,但重要的条件
LOG_INFO 信息性消息
LOG_DEBUG 调试排错消息(最低优先级)
(3)closelog函数
#include <syslog.h>
void closelog(void);

守护进程日志系统见例:syslog_dema.c


下面是国嵌的部分讲课内容:

进程特点:动态性  /并发性  /独立性   /异步性

进程互斥是指当有若干进程都要使用某一共享资源时,任何时刻最多允许一个进程使用,其他要使用该资源的进程必须等待,直到占用该资源者释放了该资源为止。

操作系统中将一次只允许一个进程访问的资源称为临界资源。

进程中访问临界资源的那段程序代码称为临界区。为实现对临界资源的互斥访问,应保证诸进程互斥地进入各自的临界区。

一组并发进程按一定的顺序执行的过程称为进程间的同步。具有同步关系的一组并发进程称为合作进程,合作进程间互相发送的信号称为消息或事件。

进程调度:按一定算法,从一组待运行的进程中选出一个来占有CPU运行。
调度方式:
• 抢占式
• 非抢占式

调度算法

  • 先来先服务调度算法
  • 短进程优先调度算法
  • 高优先级优先调度算法
  • 时间片轮转法
死锁:多个进程因竞争资源而形成一种僵局,若无外力作用,这些进程都将永远不能再向前推进。
原创粉丝点击