5、Linux进程编程设计(知识汇集 清风浅醉)

来源:互联网 发布:编程入门书 编辑:程序博客网 时间:2024/04/28 15:53

1、进程概念

进程是一个动态实体,是程序的一次执行过程,是操作系统资源分配的基本单位。

线程基本上不拥有系统资源,它与同属于一个进程的其他线程共享进程拥有的全部资源。

Linux下可以通过ps或pstree查看当前系统中的进程。

2、进程标识

PID:

 进程控制符(PID),英文全称为Process Identifier。也常被称为进程标识符。顾名思义,它是各进程的身份标识,程序一运行系统就会自动分配给进程一个独一无二的PID。进程中止后PID被系统回收,可能会被继续分配给新运行的程序。进程的标识信息可通过函数获得,这些函数声明在unistd.h头文件中。列表如下:

(1)、pid_t getpid(id)      获得目前进程的进程ID

              定义函数:pid_t getpid(void)
              返回值:返回当前进程的进程识别号。
              头文件:#include <unistd.h>

(2)、pid_t getppid(id)   获得父进程ID

              功能:用来获取目前进程的父进程标识。
              定义函数:pid_t getppid(void)
              返回值:返回当前进程的父进程识别号。           
              头文件:#include <unistd.h>

(3)、pid_t getuid()         获得进程的实际用户ID(标识运行该进程的用户)

             功能:获得进程的实际用户ID
             定义函数:pid_t getuid(void) 
             返回值:返回进程的实际用户ID
             实际用户ID(uid) :标识运行该进程的用户例如:一个普通用户A,运行了一个程序,而这个程序是以root 身份来运行的,则     程序运行时就具有root 权限。此时,实际用户ID时A用户的ID,而有效用户ID是root用户ID
             头文件:#include <unistd.h>

(4)、 geteuid()
            功能:获得进程的有效用户ID
            定义函数:pid_t geteuid(void) 
            返回值:返回进程的有效用户ID
            头文件:#include <unistd.h>
(5)、getpgid()
           功能:用来获得参数pid指令进程所属于的组识别号,若参数为0,则返回当前进程的组识别码。
           定义函数:pid_t getpgid(pid_t pid)
           返回值:执行成功则返回正确的组识别码,若有错则返-1,错误原因存在于errno中。
           头文件:#include <unistd.h>
(6)、getpgrp()
           功能:用来获得目前进程所属于的组识别号,等价于getpgid(0)。
           定义函数:pid_t getpgrp(void)
           返回值:执行成功则返回正确的组识别码。
           头文件:#include <unistd.h>
(7)、getpriotity(void)
          功能:用来获得进程,进程组和用户的进程执行优先权。
          定义函数:int getpriority(int which,int who)
          参数含义:
                  which:
                  PRIO_PROCESS   who为进程的识别码
                  PRIO_PGRP     who为进程的组识别码
                  PRIO_USER     who为用户识别码
           返回值:执行成功则返回当前进程的优先级(-20--20),值越小优先级越高。若出错则返-1,原因在errno中。
           头文件:#include <sys/resource.h>

3、Linux进程状态

在include/linux/sched.h 中我们可以看到Linxu中进程状态的具体实现:
       #define TASK_RUNNING                       0
       #define TASK_INTERRUPTIBLE          1
       #define TASK_UNINTERRUPTIBLE     2
       #define TASK_ZOMBIE                          4
       #define TASK_STOPPED                      8
其中:
       TASK_RUNNING,运行状态:进程正在运行或在运行队列中等待运行 。
       TASK_INTERRUPTIBLE,可中断等待状态:进程正在等待某个事件完成(如等待数据到达)。等待过程中可以被信号或定时器唤醒。
       TASK_UNINTERRUPTIBLE,不可中断等待状态:进程正在等待某个事件完成并且等待中不可以被信号或定时器唤醒,必须一直等待到事件发生。
       TASK_ZOMBIE,僵死状态:进程已终止,但进程控制块尚未注销,进程描述符依然存在,直到父进程调用wait()函数后释放。
       TASK_STOPPED,挂起/停止状态:进程因为收到SINSTOP,SIGSTP,SIGTIN,SGIOU信号后停止运行或者该进程正在被跟踪。

注:ps命令可以查看进程的当前状态:运行

[......]$ ps  -eo  pid,stat

PID  STAT

         1  S

         2  S<   (后缀字符解释:<  高优先级进程)

         3  SN   (后缀字符解释:N 低优先级进程)

         4  SL   (后缀字符解释:L 内存锁页)

         5  Ss   (后缀字符解释:s 该进程为会话首进程)

         6  Sl    (后缀字符解释:l  多线程进程)

         7  S+   (后缀字符解释:+ 进程位于前台进程组)

4、进程控制

fork        创建一个新进程

exit         终止进程

exec      执行一个应用程序

wait       将父进程挂起,等待子进程终止

getpid    获取当前进程ID

nice       用于改变进程的优先级

5、程序转化为进程的步骤:

(1)、内核将程序读入内存,为程序分配内存空间

(2)、内核为该进程分配进程标识符(PID)和其他所需资源

(3)、内核为该进程保持PID及相应的状态信息,把进程放到运行队列中等待执行。程序转化为进程后就可以被操作系统的调度程序调度执行了。

二者区别:

可执行程序位于磁盘中、没有堆栈、静态的、不变的。

内存映像位于内存中、动态变化。

//====进程相关函数==============(转xuejianhui)一个具体的进程程序=======================================#include <unistd.h>//成功返回进程ID,否则返回-1。pid_t getpid(void); //获取进程IDpid_t getppid(void);//获取父进程IDpid_t getuid(void); //获取进程用户IDpid_t geteuid(void);//获取进程有效用户IDpid_t getgid(void); //获取进程组IDpid_t getegid(void);//获取进程有效组IDpid_t fork(void);   //对父进程,返回新创子进程ID;对子进程,返回0;失败返回-1。    //子进程复制父进程的堆栈段和数据段的内容,但和父进程共用代码段。    // 父子进程的不同点:    // 1. 进程ID和父进程ID不同,但调度机会均等。    // 2. 子进程的time_utime,time_stime,time_cutime,time_ustime被清零。    // 3. fork函数返回值不同。    // 4. 文件锁不会被继承。    // 5. 子进程清理未处理的闹钟信号和未决信号。    // 导致fork失败的原因:    // 1.系统已有太多进程;    // 2.调用fork函数的用户的进程太多;pid_t vfork(void);    //创建公用父进程地址空间的子进程    // vfork 与 fork 的区别:    // vfork 产生的子进程与父进程公用地址空间,可影响父进程。更像是线程。    // vofrk 产生的子进程一定先运行,父进程等待子进程运行完以后再运行。    // 注意:不要在任何函数中调用vfork;int setuid(uid_t uid);    //改变进程的实际用户ID和有效用户ID,成功返回0;失败返回-1.#include <stdlib.h>void exit(int status);  //退出线程    // 可使用"echo $?"命令来查看程序退出值    // return 1; == exit(1);#include <sys/wait.h>pid_t wait(int *statloc);//获取子进程结束状态    // 返回子进程ID,并将结束信息保存在statloc指向的内存空间    // 状态:                  判断宏 :                  取值宏:    // 进程正常退出 : WIFEXITED(status)       WEXITSTATUS(status)    // 进程异常退出 : WIFSIGNALED(status)   WTERMSIG(status)    // 进程暂停       : WIFSTOPPED(status)    WSTOPSIG(status)    // 只能等待第一个退出的进程pid_t waitpid(pid_t pid, int *statloc, int options);//用于等待指定的进程退出    //参数pid : -1,任意子进程; >0,进程ID == pid; 0, 组ID == pid; < -1, 组ID == pid绝对值;    //参数options :     //        WCONTINUED  : 当子进程在暂停后继续运行,且其状态未上报,则返回其状态;     //        WNOHANG     : 当所等待的进程未结束运行时阻塞,waitpid直接返回;    //        WUNTRACED   : 子进程暂停时,其状态一直未上报,则返回其状态;#include <signal.h>void (*signal(int signo, void (*func) (int))) (int);    //参数signo : 需要加载处理的信号编号,例如SIGKILL等.编号是整数宏,定义在signal.h中。    //参数func  : 函数指针,捕捉信号后的响应函数,该参数有以下3个可能值 :    // SIG_IGN : 忽略该信号,该宏在signal.h中定义: #define SIG_IGN ((void *)(*)()) 1    // SIG_DFL : 默认处理方式,该宏在signal.h中定义: #define SIG_IGN ((void *)(*)()) 0    // 其他已定义的指针函数 : 信号处理函数原型: void handler(int);    // 函数的返回值是一个函数指针,该函数指向上一次的信号处理程序。该函数与参数func表示的一致    // 如果出错,则signal返回SIG_ERR(在signal.h中定义,#define SIG_ERR ((void *)(*)()) -1)。    //由于signal函数过于复杂,使用typedef进行如下简化:    typedef void HANDLER (int);    HANDLER *signal (int signo, HANDLER *handler);    //Linux不允许用户创建新信号,但提供SIGUSR1和SIGUSR2专门用于应用程序间进行信号通信。int kill(pid_t pid, int signo);//向另外一个进程发送信号//参数pid的取值及语义:// >0,发送给进程ID为pid的进程;// =0,发送给进程组ID和该进程相同的进程;// <0,发送给进程组内进程ID为pid的进程;//=-1,发送给系统所有的进程;//--------------------------------------------------------------------------------------
6、进程操作

(1)、创建进程

a、进程创建两种方式:操作系统创建、父进程创建。

b、子进程创建以后,父进程和子进程争夺CPU,抢到CPU者执行,另一个挂起等待。若需要父进程等待子进程执行完毕以后再继续执行,可以在fork操作后调用wait或waitpid函数。

c、fork函数一次调用有两个返回值,一是父进程调用fork函数后的返回值,该返回值是刚刚创建的子进程ID,另一个是子进程中fork函数的返回值0,创建失败只返回-1。

d、fork之后父进程和子进程哪一个先执行是不确定的,取决于调度算法。

e、如果一个子进程的父进程先于子进程结束,子进程就成为一个孤儿进程,它由init进程收养,成为init进程的子进程。

f、vfork()与fork()的区别

fork创建一个子进程是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。系统开销大。

vfork创建的子进程共享父进程的的地址空间,也就是完全运行在父进程的地址空间上。子进程修改数据,父进程也就修改了。vfork保证子进程先运行,当调用exit或exec之后,父进程才可能被调度运行。系统开销小。(此种情况下,不要容许子进程修改与父进程共享的全局变量和局部变量)

注:

#include <stdio.h>

#include <sys/types.h>

#include <unistd.h>

int main()

{

pid_t  pid;

char*  msg;

int  k;

printf("....\n");

pid  =  fork();

switch(pid){

case 0:

   msg  =  "hhhh";

   k = 3;

   break;

case -1:

  perror("...failed");

  break;

default:

  msg  =  "ppp";

  k =  5;

  break;

}

while(k > 0)

{

puts(msg);

sleep(1);

k--;

}

exit(0);

}

(2)、创建守护进程

守护进程是指在后台运行的、没有控制终端与之相连的进程,通常周期性的执行某种任务。

守护进程的启动方式:a、启动脚本/etc/rc.d中启动。b、作业规划进程crond启动。c、终端执行(shell)。

编写守护进程要点:

a、让进程后台执行。fork产生一个子进程然后让父进程退出。

b、调用setsid是进程成为一个新的会话组长和进程组长。

c、禁止进程重新打开控制终端。再一次由fork创建新的子进程,使调用fork的进程退出。

d、关闭不再需要的文件描述符,否则浪费资源。

e、将当前目录更改为根目录。

f、将文件创建时使用的屏蔽字设置为0.用umask(0)将屏蔽字清零。

g、处理SIGCHLD信号。

创建守护进程方法举例:

#incldue <stdio.h>

...

int init_daemon(void)

{

int  pid;

int  i;

/*忽略终端I/O信号,STOP信号*/

signal(SIGTTOU,SIG_IGN);

signal(SIGTTIN,SIG_IGN);

signal(SIGTSTP,SIG_IGN);

signal(SIGHUP,SIG_IGN);

pid  =  fork();

if(pid > 0){

exit(0);//结束父进程,使得子进程成为}

else if(pid < 0){

return -1;}

/*建立一个新的进程组,子进程成为这个进程组的首进程,以使该进程脱离所有终端*/

setsid();

/*再次新建一个子进程,退出父进程,保证该进程不是进程组长,同时让该进程无法再打开一个新的终端*/

pid = fork();

if(pid > 0){

         exit(0);

}

else if(pid < 0){

         return -1;

}

/*关闭所有从父进程继承的不再需要的文件描述符*/

for(i=0; i<NOFILE;close(i++))

/*改变工作目录,使得进程不与任何文件系统联系*/

chdir(“/”);

/*将文件屏蔽字设置为0*/

umask(0);

/*忽略SIGCHLD信号*/


return 0;

}

注:chdir 是C语言中的一个系统调用函数(同cd)

功 能:更改当前工作目录。
参 数:Path 必选。Path 可能包含驱动器。如果未指定驱动器,则当前驱动器上的默认目录或文件夹。
返回值:成功返回0 ,失败返回-1

注:进程退出

正常退出  return  exit  _exit

异常退出  调用abort  进程收到某个信号,而该信号使程序终止

exit在头文件stdlib.h中,调用exit()要先执行一些清除操作,然后将控制权交给操作系统。_exit()声明在unistd.h中,执行后会立即返回给操作系统。

7、执行新程序

        使用fork或vfork创建子进程后,我们希望子进程去执行其他程序,exec函数族就提供了一个在进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段,在执行完之后,原调用进程的内容除了进程号外,其他全部被新程序的内容替换了。对系统而言,不过执行的是另外一个程序了。

a、exec函数族如下:

int   exec…装入和运行其它程序:
int   execl(  char *pathname,char *arg0,char *arg1,...,char *argn,NULL)
int   execle( char *pathname,char *arg0,char *arg1,...,char *argn,NULL,char *envp[])
int   execlp( char *pathname,char *arg0,char *arg1,...,NULL)
int   execlpe(char *pathname,char *arg0,char *arg1,...,NULL,char *envp[])
int   execv(  char *pathname,char *argv[])
int   execve( char *pathname,char *argv[],char *envp[])
int   execvp( char *pathname,char *argv[])
int   execvpe(char *pathname,char *argv[],char *envp[])

事实上,这6个函数中真正的系统调用只有execve,其他5个都是库函数,它们最终都会调用execve这个系统调用,调用关系如下图所示:


b、环境变量:

为了更好的理解exec函数族的使用,首先要理解环境变量这个概念:环境变量包括用户主目录、终端类型、当前目录等,他们定义了用户的工作环境。env命令可以查看环境变量值或修改这些变量值。在每个进程启动时,都会接到一张环境表。环境表是一个字符指针数组,其中每个指针包含一个以 null 结束的 C 字符串的地址。全局变量environ 则包含了该指针数组的地址,

extern char **environ;
例如,下图显示了包含有5个环境字符串的环境表


c、环境变量值获取函数(转)
ISO C 定义了一个函数 getenv,用于获取环境变量值。
#include <stdlib.h>
char *getenv(const char *name);

返回值:指向与name关联的value的指针,若为找到则返回NULL


POSIX.1 定义了两个函数 putenv 和 setenv,用于设置环境变量,
#include <stdlib.h>
int putenv(char *str);

返回值:若成功则返回0,若出错则返回非0值


putenv 的参数是形式为 name=value 的字符串的指针,将参数放到环境表中。如果 name 已经存在,则先删除其原有的定义。
#include <stdlib.h>
int setenv(const char *name, const char *value, int rewrite);

返回值:若成功则返回0,若出错则返回-1

setenv 函数将环境变量 name 的值设置为 value。如果环境表中 name 已存在,那么
• 若 rewrite 为非 0 值,则首先删除其现有定义。

• 若 rewrite 为 0,则不删除其现有定义,name 不设置为新的 value,也不出错。


另外提供了 unsetenv 函数用于删除环境变量,
#include <stdlib.h>
int unsetenv(const char *name);
返回值:若成功则返回0,若出错则返回-1

unsetenv 函数删除 name 的定义,即使不存在环境变量 name 也不出错。


exec函数用法举例:
int main(int argc, char *argv[], char **environ)//这是main函数最完整的形式

{

pid_t  pid;

int  stat_val;


pid = fork();

switch(pid){

case -1:

perror("..error.");

exit(1);//异常退出,控制权归还给操作系统

case 0:

printf("uid=%d,gid=%d", getuid(),getgid());

execve("processimage", argv, environ);//该函数,使得进程执行新程序

printf("process never go to here\n")://此处子进程执行不到这里

exit(0);//正常退出,控制权归还给操作系统

default:

...

break;

}

wait(&stat_val);//如果父进程没有调用wait和waitpid函数,子进程会进入僵死状态,调用了就不会了。

exit(0);

}

注:wait函数使父进程暂停执行,直到它的一个子进程结束为止,该函数的返回值是终止运行的子进程PID。wait等待第一个终止的子进程,而waitpid则可以指定等待特定的子进程。
8、进程其他操作
(1)、获得进程ID
getpid();
举例:printf("get ID %d",getpid());
(2)、设置实际用户ID和有效用户ID
     int  setuid(uid_t uid)
使用方法:若进程具有root权限,则函数将实际用户ID、有效用户ID设置为参数uid。若进程不具有root权限,但uid等于实际用户ID,则setuid只将有效用户ID设置为uid,不改变实际用户ID。其他情况函数调用失败,返回-1并将erron设置为EPERM。setuid()用来重新设置执行目前进程的用户识别码。不过,要让此函数有作用,其有效的用户识别码必须为0(root)。
(3)、设置实际组ID和有效组ID
     int  setgid(gid_t gid)
(4)、改变进程优先级
在Linux下通过系统调用nice可以改变进程的优先级:
a、int  getpriority(int which, int who)
该函数返回一组进程的优先级,which和who组合确定返回哪一组进程的优先级。
参数:
PRIO_PROCESS:一个特定的进程,此时who取值为进程ID
PRIO_PGRP:一个进程组的所有进程,who为进程组的ID
PRIO_UESR:一个用户拥有的所有进程,who取值为实际用户ID
b、int  setpriority(int which, int who, int prio)
该函数用来设置指定的进程优先级,函数返回指定进程的优先级。
nice用法代码举例:
int main(void)
{
pid_t pid;
int  stat_val = 0;
int  oldpri,newpri;
pid = fork();
switch(pid){
case 0:
oldpri =  getpriority(PRIO_PROCESS, 0);
newpri = nice(2);//此处改变了进程的优先级,设置为2
...
}
}


0 0
原创粉丝点击