Linux 进程(一):环境及其控制

进程环境

main启动

当内核执行C程序时,在调用main前先调用一个特殊的启动例程。可执行程序将此启动例程指定为程序的起始地址,接着启动例程从内核中取出命令行参数和环境变量值,然后执行main函数。

进程终止

使进程终止的方式有8种,其中5种为正常终止,3种为异常终止:


终止类型


说明


正常终止


(1)   从main返回

(2)   调用exit

(3)   调用_exit或_Exit

(4)   最后一个线程从启动例程返回

(5)   在最后一个线程中调用pthread_exit


异常终止


(1)   调用abort

(2)   接收到一个信号

(3)   最后一个线程对取消请求做出响应

相关函数:


#include <stdlib.h>

void exit(int status);

void _Exit(int status);

#include <unistd.h>

void _exit(int status);

说明:

在main中,return(0)与exit(0)是等价的。

exit函数总是执行一个标准I/O库的清理关闭工作,即对所有打开的流调用fclose函数。

#include <stdlib.h>

int atexit(void (*func)(void));

返回值:成功,0;失败,-1

说明:

atexit函数用于向进程注册函数,最多可以注册32个。这些函数将由exit调用。我们将这些函数称为终止处理函数。如果同一个函数登记了多次,则也会被调用多次。

exit调用顺序与这些函数的注册顺序刚好相反

# atexit函数的使用
[[email protected] IO]# cat atexit.c
#include <stdio.h>
#include <stdlib.h>

static void my_exit1(void);
static void my_exit2(void);

int main(void)
{
        if(atexit(my_exit1) != 0)
                printf("can‘t register my_exit1\n");
        if(atexit(my_exit1) != 0)
                printf("can‘t register my_exit1\n");
        if(atexit(my_exit2) != 0)
                printf("can‘t register my_exit2\n");

        printf("main is done\n");
        return 0;
}

static void my_exit1(void)
{
        printf("first exit handler\n");
}

static void my_exit2(void)
{
        printf("second exit handler\n");
}

[[email protected] IO]# ./atexit
main is done
second exit handler
first exit handler
first exit handler

环境变量

每个程序都有一张环境表,是一个字符指针数组,每个指针包含一个以null结束的字符串的地址。全局变量extern char** environ指向该环境表的地址:

如下函数用于操作环境变量的值:


#include <stdlib.h>

char* getenv(const char* name);

返回值:成功,返回与name关联的value;失败,返回NULL

int setenv(const char* name, const char* value, int rewrite);

int unsetenv(const char* name);

int putenv(char* str);

返回值:成功,0;失败,-1

说明:

putenv将name=value的字符串放到环境表中。如果name已经存在,则先删除其原来的定义。

关于参数rewrite:如果rewrite为0,则保留原有的name定义;rewrite非0,则覆盖原有的name定义。

unsetenv用于删除指定的name定义。

通常用getenv和putenv来访问特定的环境变量,而非使用environ变量,但是如果要查看整个环境,则仍然需要使用environ,如下:

#include <stdio.h>

int main(void)
{
        extern char** environ;

        while(*environ != NULL)
        {
                printf("%s\n", *environ);
                environ++;
        }

        return 0;
}

[[email protected] IO]# ./environ
HOSTNAME=benxintuzi
SELINUX_ROLE_REQUESTED=
SHELL=/bin/bash
TERM=vt100
HISTSIZE=1000
HADOOP_HOME=/bigdata/hadoop-2.6.0
SSH_CLIENT=192.168.8.1 55561 22
SELINUX_USE_CURRENT_RANGE=
QTDIR=/usr/lib/qt-3.3
QTINC=/usr/lib/qt-3.3/include
SSH_TTY=/dev/pts/1
USER=benxintuzi
...

程序的存储布局

C程序由以下几部分组成:

正文段:存放代码的地方,通常是可共享的,即该部分内容是可再入的,因此一般只需一份副本,并且设置为只读。

初始化数据段:包含程序中需要明确指定初值的变量,比如:int maxcount = 99;

未初始化数据段(bss段):该段的数据不指定初值也可,因为内核在程序开始执行前,会将此段中的数据初始化为0或者NULL。该部分有时也被称为常量区或全局区。

栈区:存放临时变量,函数地址或者环境上下文信息等。

堆区:动态存储分配,位于bss段和栈区之间。

一个典型的存储空间示意图如下:

Linux中的size程序用户报告一个程序所占用的存储空间信息:


[[email protected] IO]# size passwd01 memstr

text    data     bss     dec     hex   filename

1360    264      8       1632    660    passwd01

2157    284      8       2449    991    memstr

动态存储分配函数如下:


#include <stdlib.h>

void* malloc(size_t size);

void* calloc(size_t nobj, size_t size);

void* realloc(void* ptr, size_t newsize);

void free(void* ptr);

说明:

malloc分配指定字节数的存储区,初始值不确定。

calloc分配指定长度的存储区,初始值为0。

realloc增加或者减少存储区的长度,新增的区域内初始值不确定。

资源限制

每个进程都有一组资源限制,可以使用如下函数查询和更改:


#include <sys/resource.h>

int getrlimit(int resource, struct rlimit* rlptr);

int setrlimit(int resource, const struct rlimit* rlptr);

返回值:成功,0;失败,-1

struct rlimit

{

rlim_t rlim_cur;  /* soft limit: current limit */

rlim_t rlim_max;  /* hard limit: maximum value for rlim_cur */

}

说明:

任何一个进程都可将一个软限制值更改为小于等于其硬限制值。

任何一个进程都可降低其硬限制值,但它不能小于软限制值,而且这种降低对于普通用户是不可逆的。

只有超级用户进程可以提高硬限制值。

resource参数取值如下:

RLIMIT_AS:进程总的可用存储空间的最大长度。

RLIMIT_CORE:core文件的最大字节。若为0,则阻止创建core文件。

RLIMIT_CPU:CPU时间的最大量值(秒)。若超过此软限制,则向该进程发送SIGXCPU信号。

RLIMIT_DATA:数据段总的最大字节长度,包含:初始化数据段、bss段、堆区。

RLIMIT_FSIZE:可以创建文件的最大字节长度。若超过此软限制,则向该进程发送SIGXFSZ信号。

RLIMIT_MEMLOCK:进程使用mlock能够锁定的在存储空间中的最大字节长度。

RLIMIT_MSGQUEUE:进程为消息队列可分配的最大存储字节数。

RLIMIT_NICE:nice值影响进程的调度优先级。

RLIMIT_NOFILE:进程可打开的最大文件数量。更改此限制将影响sysconf函数在参数_SC_OPEN_MAX中的返回值。

RLIMIT_NPROC:每个用户ID可拥有的最大子进程数。更改此限制将影响sysconf函数在参数_SC_CHILD_MAX中的返回值。

RLIMIT_RSS:最大驻留内存集字节长度。

RLIMIT_SIGPENDING:进程可排队的信号最大数量。

RLIMIT_STACK:栈的最大字节长度。

RLIMIT_SWAP:用户可消耗的交换空间的最大字节数。

如下程序打印当前系统支持的所有资源限制情况:

[[email protected] IO]# cat reslimit.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/resource.h>

#define doit(name)      pr_limits(#name, name)

static void     pr_limits(char*, int);

int main(void)
{
#ifdef  RLIMIT_AS
        doit(RLIMIT_AS);
#endif

        doit(RLIMIT_CORE);
        doit(RLIMIT_CPU);
        doit(RLIMIT_DATA);
        doit(RLIMIT_FSIZE);

#ifdef  RLIMIT_MEMLOCK
        doit(RLIMIT_MEMLOCK);
#endif

#ifdef RLIMIT_MSGQUEUE
        doit(RLIMIT_MSGQUEUE);
#endif

#ifdef RLIMIT_NICE
        doit(RLIMIT_NICE);
#endif

        doit(RLIMIT_NOFILE);

#ifdef  RLIMIT_NPROC
        doit(RLIMIT_NPROC);
#endif

#ifdef RLIMIT_NPTS
        doit(RLIMIT_NPTS);
#endif

#ifdef  RLIMIT_RSS
        doit(RLIMIT_RSS);
#endif

#ifdef  RLIMIT_SBSIZE
        doit(RLIMIT_SBSIZE);
#endif

#ifdef RLIMIT_SIGPENDING
        doit(RLIMIT_SIGPENDING);
#endif

        doit(RLIMIT_STACK);

#ifdef RLIMIT_SWAP
        doit(RLIMIT_SWAP);
#endif

#ifdef  RLIMIT_VMEM
        doit(RLIMIT_VMEM);
#endif

        exit(0);
}

static void pr_limits(char* name, int resource)
{
        struct rlimit           limit;
        unsigned long long      lim;

        if (getrlimit(resource, &limit) < 0)
                printf("getrlimit error for %s\n", name);
        printf("%-14s  ", name);
        if (limit.rlim_cur == RLIM_INFINITY) {    # 常量RLIM_INFINITY表示无限制
                printf("(infinite)  ");
        } else {
                lim = limit.rlim_cur;
                printf("%10lld  ", lim);
        }
        if (limit.rlim_max == RLIM_INFINITY) {
                printf("(infinite)");
        } else {
                lim = limit.rlim_max;
                printf("%10lld", lim);
        }
        putchar((int)‘\n‘);
}

[[email protected] IO]# ./reslimit
RLIMIT_AS       (infinite)  (infinite)
RLIMIT_CORE              0  (infinite)
RLIMIT_CPU      (infinite)  (infinite)
RLIMIT_DATA     (infinite)  (infinite)
RLIMIT_FSIZE    (infinite)  (infinite)
RLIMIT_MEMLOCK       65536       65536
RLIMIT_MSGQUEUE      819200      819200
RLIMIT_NICE              0           0
RLIMIT_NOFILE         1024        4096
RLIMIT_NPROC          3861        3861
RLIMIT_RSS      (infinite)  (infinite)
RLIMIT_SIGPENDING        3861        3861
RLIMIT_STACK      10485760  (infinite)

进程控制

进程标识

每个进程都用一个非负整型ID唯一标识,但是该进程ID是可复用的,只不过大多数Unix系统采用延迟复用算法,即如果一个进程终止后,必须经过某个固定的间隔才可将该进程ID用于表示其他进程,这样做可以防止将新进程误认为是前一个已经终止的进程。

特殊进程:


进程ID


说明


0


用于标识调用进程(交换进程swapper),其并不执行任何磁盘上的程序,因此也被称为系统进程。


1


用于标识init进程,在自举结束时由内核调用,位于/sbin/init,用于在自举后启动一个Unix系统。init通常读取与系统有关的初始化文件(/etc/rc*或/etc/inittab、/etc/init.d),将系统引导到一个状态。init进程不会终止,但是其并不是系统进程,而是一个普通的用户进程,只不过是以超级用户特权运行。

其他ID标识操作函数:


#include <unistd.h>

pid_t getpid(void);

pid_t getppid(void);

uid_t getuid(void);

uid_t geteuid(void);

gid_t getgid(void);

gid_t getegid(void);

说明:

getpid返回进程ID,getppid返回父进程ID,getuid返回实际用户ID,geteuid返回有效用户ID,getgid返回实际组ID,getegid返回有效组ID。

进程创建


函数


说明


#include <unistd.h>

pid_t fork(void);


(1)   fork函数用于子进程的创建,调用一次,返回两次。父进程返回子进程ID,而子进程返回0。

(2)   子进程是父进程的副本,获取父进程的数据、堆、栈等的副本(不共享)。父子进程共享正文段。

(3)   由于在fork之后经常伴随着exec,所有现在很多fork实现并不执行数据、堆和栈的完全副本,而是采用写时复制(Copy-On-Write, COW)技术,即这些区域先由父子进程共享,内核将他们的权限设为只读,如果其中任何一个需要修改时,内核只为需要修改的区域部分制作一个副本。

(4)   在fork之后先执行父进程还是子进程是不确定的,取决于内核使用的调度算法。

[[email protected] process]# cat fork01.c
#include <unistd.h>
#include <stdio.h>

int    globvar = 6;            /* external variable in initialized data */
char    buf[] = "a write to stdout\n";

int main(void)
{
        int             var;            /* automatic variable on the stack */
        pid_t   pid;

        var = 88;
        if (write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1)
                printf("write error\n");

        printf("before fork\n");        /* we don‘t flush stdout */

        if ((pid = fork()) < 0)
        {
                printf("fork error\n");
        }
        else if (pid == 0)
        {                                               /* child */
                globvar++;                              /* modify variables */
                var++;
        }
        else
        {
                sleep(2);                               /* parent */
        }

        printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar, var);

        return 0;
}

[[email protected] process]# ./fork01
a write to stdout
before fork
pid = 2270, glob = 7, var = 89
pid = 2269, glob = 6, var = 88

fork失败的两个主要原因:

(1)   系统中进程数量太多了。

(2)   该实际用户ID所拥有的进程数超过了系统限制(由CHILD_MAX指定)。


vfork


vfork函数用于创建一个新进程来执行一个新程序。vfork保证子进程先运行,在其调用exec或exit之后父进程才可能被调用执行,所以如果在调用这两个函数之前,子进程依赖于父进程的进一步动作,则会导致死锁。

将上述程序用vfork重写,由于vfork可以保证在调用exec或者exit之前,父进程不会执行,因此不用在父进程中调用sleep休眠了:

[[email protected] process]# cat vfork.c
#include <unistd.h>
#include <stdio.h>

int             globvar = 6;            /* external variable in initialized data */
char    buf[] = "a write to stdout\n";

int main(void)
{
        int             var;            /* automatic variable on the stack */
        pid_t   pid;

        var = 88;
        if (write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1)
                printf("write error\n");

        printf("before vfork\n");       /* we don‘t flush stdout */

        if ((pid = vfork()) < 0)
        {
                printf("fork error\n");
        }
        else if (pid == 0)
        {                                               /* child */
                globvar++;                              /* modify variables */
                var++;
                exit(0);                                /* child terminates */
        }
        /* parent continues here */
        printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar, var);

        return 0;
}

[[email protected] process]# gcc vf./vfork
a write to stdout
before vfork
pid = 2295, glob = 7, var = 89

进程终止

进程有5种正常终止方式以及3种异常终止方式:

5种正常终止方式:


(1)   在main函数内执行return语句,等效于调用exit。

(2)   调用exit函数。exit操作包括调用各种终止处理函数(exit handler)。

(3)   调用_exit或_Exit函数。_Exit的存在是为进程提供一种无需运行终止处理函数或者信号处理函数而终止的方式。_exit函数是由exit函数调用的,用于处理Unix系统特定的细节。

(4)   进程的最后一个线程在其启动例程中执行return语句。但是该线程的返回值不用作进程的返回值。当最后一个线程从其启动例程返回时,该进程以终止状态0返回。

(5)   进程的最后一个线程调用pthread_exit函数。

3种异常终止方式:


(1)   调用abort,其产生SIGABRT信号。

(2)   进程接收到某些信号时。

(3)   最后一个线程对“取消”请求做出响应。

不管进程如何终止,最后都会执行内核中的同一段代码,这段代码为相应进程关闭所有打开的文件描述符,释放其所占用的存储器等。

获取进程终止状态

当一个进程正常或异常终止时,内核就会向其父进程发送SIGCHLD信号,进而父进程调用wait或waitpid获取其子进程的终止状态。调用wait时可能有如下情况发生:

(1)   如果当前进程没有任何子进程,则立即出错返回。

(2)   如果所有子进程都在运行,则阻塞。

(3)   如果其中一个子进程已经终止,则取得该子进程的终止状态后立即返回。


#include <sys/wait.h>

pid_t wait(int* statloc);

pid_t waitpid(pid_t pid, int* statloc, int options);

返回值:成功,返回进程ID;失败,返回0或-1

说明:

进程的终止状态存放于statloc指向的存储空间中。

<sys/wait.h>中定义了4个宏,用来获取进程的终止状态:

WIFEXITED(status)标识正常终止;

WIFSIGNALED(status)标识异常终止;

WIFSTOPPED(status)标识当前暂停子进程;

WIFCONTINUED(status)标识暂停后继续执行的子进程;

关于参数pid:

pid == -1: 等待任一子进程,此时waitpid与wait等效。

pid > 0: 等待与pid相等的子进程。

pid == 0: 等待组ID与调用进程组ID相等的任一子进程。

pid < -1: 等待组ID等于pid绝对值的任一子进程。

waitpid与wait的比较:

(1)   waitpid可以等待一个特定的进程,而wait只能返回任一终止子进程的状态。

(2)   waitpid提供了一个wait的非阻塞版本。

(3)   waitpid通过WUNTRACED/WCONTINUED选项支持作业控制。

[[email protected] process]# cat wait01.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>

void pr_exit(int status);

int main(void)
{
        pid_t   pid;
        int             status;

        if ((pid = fork()) < 0)
                printf("fork error\n");
        else if (pid == 0)                              /* child */
                exit(7);

        if (wait(&status) != pid)               /* wait for child */
                printf("wait error\n");
        pr_exit(status);                                /* and print its status */

        if ((pid = fork()) < 0)
                printf("fork error\n");
        else if (pid == 0)                              /* child */
                abort();                                        /* generates SIGABRT */

        if (wait(&status) != pid)               /* wait for child */
                printf("wait error\n");
        pr_exit(status);                                /* and print its status */

        if ((pid = fork()) < 0)
                printf("fork error\n");
        else if (pid == 0)                              /* child */
                status /= 0;                            /* divide by 0 generates SIGFPE */

        if (wait(&status) != pid)               /* wait for child */
                printf("wait error\n");
        pr_exit(status);                                /* and print its status */

        exit(0);
}

void pr_exit(int status)
{
        if (WIFEXITED(status))
                printf("normal termination, exit status = %d\n", WEXITSTATUS(status));
        else if (WIFSIGNALED(status))
                printf("abnormal termination, signal number = %d\n", WTERMSIG(status));
        else if (WIFSTOPPED(status))
                printf("child stopped, signal number = %d\n", WSTOPSIG(status));
}

[[email protected] process]# ./wait01
normal termination, exit status = 7
abnormal termination, signal number = 6
abnormal termination, signal number = 8

另一个取得进程终止状态的函数是waitid,与waitpid相似,但waitid允许一个进程指定要等待的子进程。它使用两个参数表示要等待的子进程所属的类型。


#include <sys/wait.h>

int waitid(idtype_t idtype, id_t id, siginfo_t* infop, int options);

返回值:成功,返回0;失败,返回-1

说明:

idtype参数可以如下:

P_PID:等待一个特定进程,id中包含要等待子进程的进程ID。

P_PGID:等待一个特定进程组的任一子进程,id中包含要等待子进程的进程组ID。

P_ALL:等待任一子进程,此时忽略id。

options参数如下(按位或):

WCONTINUED等待一个进程,其曾被停止,然后又继续运行,但其状态尚未报告。

WEXITED:等待已退出的进程。

WNOHANG:如果没有可用的子进程退出状态,立即返回而非阻塞。

WNOWAIT不破坏子进程退出状态,该子进程退出状态可由后续的wait/waitpid/waitid取得。

WSTOPPED等待一个进程,它已经停止,但其状态尚未报告。

注:

必须指定WCONTINUED、WNOWAIT、WSTOPPED三者之一。

infop指向一个siginfo结构,包含了造成子进程状态改变有关信号的详细信息。

函数exec

当进程调用exec函数时,exec就用磁盘上的一个新程序替换掉当前进程的正文段、数据段、堆区、栈区,然后开始执行新程序的main函数。一般调用fork后立即会调用exec,表示创建新进程后,立即在该进程中执行新程序。有7种exec函数可用:


#include <unistd.h>

int execl(const char* pathname, const char* arg0, ... /* (char*)0 */);

int execv(const char* pathname, char* const argv[]);

int execle(const char* pathname, const char* arg0, ... /* (char*)0, char* const envp[] */);

int execve(const char* pathname, char* const argv[], char* const envp[]);

int execlp(const char* filename, const char* arg0, ... /* (char*)0 */);

int execvp(const char* filename, char* const argv[]);

int fexecve(int fd, char* const argv[], char* const envp[]);

返回值:成功,不返回;失败,返回-1

说明:

关于filename,如果filename中包含/,则将其视为路径名;否则就在PATH指定的目录中搜索可执行文件。

如果execlp和execvp找到了一个可执行文件,但是该文件不是由链接器产生的可执行文件,则就将其看作一个shell脚本,试着调用/bin/sh,并将该filename作为shell的输入。

函数名中的l表示列表list,v表示向量vector,e表示环境。因此execl/execlp/execle要求将新程序的每个命令行参数都作为一个独立的参数传递,而execv/execvp/execve/fexecve将其按数组方式传递。execle/execve/fexecve要求传递一个环境表指针,其他几个exec函数则使用全局变量environ为新程序复制现有的环境。

首先,查看PATH变量,发现里面有HOME=/root一项,那么将以下可执行文件echoall放到/root目录下,echoall可执行文件源程序如下所示,用于打印当前进程的环境表:
#include <stdio.h>

int main(int argc, char *argv[])
{
        int                     i;
        char            **ptr;
        extern char     **environ;

        for (i = 0; i < argc; i++)              /* echo all command-line args */
                printf("argv[%d]: %s\n", i, argv[i]);

        for (ptr = environ; *ptr != 0; ptr++)   /* and all env strings */
                printf("%s\n", *ptr);

        return 0;
}

然后使用fork创建新进程,使用exec函数运行echoall程序:
[[email protected] process]# cat exec01.c
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>

char    *env_init[] = { "USER=unknown", "PATH=/tmp", NULL };

int main(void)
{
        pid_t   pid;

        if ((pid = fork()) < 0) {
                printf("fork error\n");
        } else if (pid == 0) {  /* specify pathname, specify environment */
                if (execle("/root/echoall", "echoall", "myarg1",
                                "MY ARG2", (char *)0, env_init) < 0)
                        printf("execle error\n");
        }

        if (waitpid(pid, NULL, 0) < 0)
                printf("wait error\n");

        if ((pid = fork()) < 0) {
                printf("fork error\n");
        } else if (pid == 0) {  /* specify filename, inherit environment */
                if (execlp("/root/echoall", "echoall", "only 1 arg", (char *)0) < 0)
                        printf("execlp error\n");
        }

        return 0;
}

[[email protected] process]# ./exec01
argv[0]: echoall
argv[1]: myarg1
argv[2]: MY ARG2
USER=unknown
PATH=/tmp
[[email protected] process]# argv[0]: echoall
argv[1]: only 1 arg
...
HOME=/root
_=./exec01

更改用户ID和组ID

Unix系统中,访问控制是基于用户ID和组ID的,当程序需要访问当前并不允许访问的资源时,就需要增加特权,更换自己的用户ID和组ID,使之具有合适的特权或访问权限。

可以用setuid函数设置实际用户ID和有效用户ID,用setgid设置实际组ID和有效组ID。关于内核维护的3个用户ID,有如下说明:

(1)   只有超级用户进程才可以更改实际用户ID。通常,实际用户ID是在登录时,由login程序设置的,而login是一个超级用户进程,当它调用setuid时,设置所有3个用户ID。

(2)   仅当文件中设置了保存用户ID位时,exec函数才会设置有效用户ID。如果保存用户ID位没有设置,则exec函数不会改变有效用户ID,而是维持其现有值不变。任何时候都可以调用setuid将有效用户ID设置为实际用户ID或者保存用户ID。

(3)   保存用户ID是由exec函数复制有效用户ID而得到的。如果设置了文件的保存用户ID位,则在exec根据文件的用户ID设置了进程的有效用户ID后,这个副本就被保存起来了。

如下函数用户交换实际ID和有效ID的值:


#include <unistd.h>

int setreuid(uid_t ruid, uid_t euid);

int setregid(gid_t rgid, gid_t egid);

返回值:成功,返回0;失败,返回-1

说明:

一个非特权用户可将其有效用户ID设置为实际用户ID或者保存用户ID,一个特权用户则可将有效用户ID设置为uid,设置不同用户ID的函数图示如下:

解释器文件

解释器文件的起始行形式是:

#! pathname [optional-argument]

在!和pathname之间的空格是可选的,最常见的解释器文件开始行为:

#! /bin/sh

pathname通常是绝对路径名。对解释器文件的处理是由内核作为exec系统调用的一部分来完成的。内核使调用exec函数的进程实际执行的并不是解释器文件本身,而是解释器文件中第一行pathname指定的文件,如下所示:

[[email protected] process]# cat /root/echoall.c
#include <stdio.h>

int main(int argc, char *argv[])
{
        int                     i;
        char            **ptr;
        extern char     **environ;

        for (i = 0; i < argc; i++)              /* echo all command-line args */
                printf("argv[%d]: %s\n", i, argv[i]);

        for (ptr = environ; *ptr != 0; ptr++)   /* and all env strings */
                printf("%s\n", *ptr);

        return 0;
}

[[email protected] process]# cat /root/testinterp
#! /root/echoall echoarg1 echoarg2

[[email protected] process]# cat interp.c
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>

int main(void)
{
        pid_t pid;

        if ((pid = fork()) < 0)
                printf("fork error\n");
        else if (pid == 0)
                if (execl("/root/echoall", "/root/testinterp", "myarg1", "myarg2", (char*)0) < 0)
                        printf("execl error\n");
        if (waitpid(pid, NULL, 0) < 0)
                printf("waitpid error\n");

        return 0;
}

[[email protected] process]# ./interp
argv[0]: /root/testinterp
argv[1]: myarg1
argv[2]: myarg2
HOSTNAME=benxintuzi
SELINUX_ROLE_REQUESTED=
SHELL=/bin/bash
TERM=vt100
HISTSIZE=1000
HADOOP_HOME=/bigdata/hadoop-2.6.0
SSH_CLIENT=192.168.8.1 50293 22
SELINUX_USE_CURRENT_RANGE=
QTDIR=/usr/lib/qt-3.3
QTINC=/usr/lib/qt-3.3/include
SSH_TTY=/dev/pts/0
USER=benxintuzi

进程调度

调度策略和调度优先级是由内核控制的,进程可以通过调整nice值降低进程的调度优先级。nice越小,优先级越高。进程可以通过nice函数获取或更改其nice值,该操作只影响进程自己的nice值,不影响其他进程:


#include <unistd.h>

int nice(int lchr);

返回值:成功,返回新的nice值NZERO;失败,返回-1

说明:

nice值的范围一般为0~(2*NAERO) – 1。

新的nice值new_nice = old_nice + lchr,如果超出nice值的范围,自动降到最大、最小合法值。

#include <sys/resource.h>

int getpriority(int which, id_t who);

返回值:成功,返回-NZERO~NZERO – 1之间的nice值;失败,返回-1

说明:

getpriority函数不仅可以获取进程的nice值,还可以获取一组相关进程的nice值。

which参数如下:

PRIO_PROCESS:表示进程。

PRIO_PGRP:表示进程组。

PRIO_USER:表示用户ID。

who参数选择一个或者多个进程,如果为0,表示选择一个。

#include <sys/resource.h>

int setpriority(int which, id_t who, int value);

返回值:成功,返回0;失败,返回-1

进程时间


#include <sys/times.h>

clock_t times(struct tms* buf);

返回值:成功,返回时间;失败,返回-1

说明:

times函数填充tms结构体:

struct tms

{

clock_t    tms_utime; /* user CPU time */

clock_t tms_stime;   /* system CPU time */

clock_t tms_cutime;  /* user CPU time, terminated children */

clock_t tms_cstime;  /* system CPU time, terminated children */

};

sysconf(_SC_CLK_TCK)返回每秒时钟的滴答数。

如下函数将命令行参数作为程序运行,并且统计执行每个程序的时间:

[[email protected] process]# cat times.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/times.h>

static void     pr_times(clock_t, struct tms *, struct tms *);
static void     do_cmd(char *);

int main(int argc, char *argv[])
{
        int             i;

        setbuf(stdout, NULL);
        for (i = 1; i < argc; i++)
                do_cmd(argv[i]);        /* once for each command-line arg */
        return 0;
}

static void do_cmd(char *cmd)           /* execute and time the "cmd" */
{
        struct tms      tmsstart, tmsend;
        clock_t         start, end;
        int                     status;

        printf("\ncommand: %s\n", cmd);

        if ((start = times(&tmsstart)) == -1)   /* starting values */
                printf("times error\n");

        if ((status = system(cmd)) < 0)                 /* execute command */
                printf("system() error\n");

        if ((end = times(&tmsend)) == -1)               /* ending values */
                printf("times error\n");

        pr_times(end-start, &tmsstart, &tmsend);
        printf("status = %d\n", status);
}

static void pr_times(clock_t real, struct tms *tmsstart, struct tms *tmsend)
{
        static long             clktck = 0;

        if (clktck == 0)        /* fetch clock ticks per second first time */
                if ((clktck = sysconf(_SC_CLK_TCK)) < 0)
                        printf("sysconf error\n");

        printf("  real:  %7.2f\n", real / (double) clktck);
        printf("  user:  %7.2f\n",
          (tmsend->tms_utime - tmsstart->tms_utime) / (double) clktck);
        printf("  sys:   %7.2f\n",
          (tmsend->tms_stime - tmsstart->tms_stime) / (double) clktck);
        printf("  child user:  %7.2f\n",
          (tmsend->tms_cutime - tmsstart->tms_cutime) / (double) clktck);
        printf("  child sys:   %7.2f\n",
          (tmsend->tms_cstime - tmsstart->tms_cstime) / (double) clktck);
}

[[email protected] process]# ./times "sleep 5" "date" "man bash > /dev/null"

command: sleep 5
  real:     5.03
  user:     0.00
  sys:      0.00
  child user:     0.00
  child sys:      0.00
status = 0

command: date
Sat Aug 29 20:26:49 PDT 2015
  real:     0.01
  user:     0.00
  sys:      0.00
  child user:     0.00
  child sys:      0.01
status = 0

command: man bash > /dev/null
  real:     0.69
  user:     0.00
  sys:      0.00
  child user:     0.19
  child sys:      0.49
status = 0
[[email protected] process]#
时间: 2024-10-05 05:20:44

Linux 进程(一):环境及其控制的相关文章

Linux进程和服务的控制

一:进程及服务的控制1.什么是进程系统中正在进行的程序2.图形的进程查看方式gnome-system-monitor3.查看进程的命令ps 查看进程a 当前进程相关进程(包含了当前环境本身信息)-a 显示当前终端中的所有进程(不包含当前环境本身信息)-A|-e 系统所有进程x 包含含有终端的所有(必须含有tty(字符输出设备的))进程f 查看进程的从属关系u 进程的所有人ps ax -o comm,nice,%cpu,%mem,pid,user,group,stat 查看...ps ax --s

linux进程信号查看与控制

kill 发送信号将其终止kill命令格式:kill -Signal pidsignal是发送给进程的信号pid是进程号,可以用 ps 命令查出 kill -信号 进程pid强制杀死进程9396killall -信号 进程名字使用进程名一次性杀死所有指定进程pkill -u user -信号开启两个终端,一个切换用户tom,另一个使用查看进程命令ps指定用户名和PID,通过管道符抓取tom相关进程,指定用户名将其进程杀掉 kill -l 查看系统中定义的信号列表man 7 signal 查看信号

六、Linux进程控制

1. Linux进程概述 进程是一个程序一次执行的过程,它和程序有本质区别. 程序是静态的,它是一些保存在磁盘上的指令的有序集合:而进程是一个动态的概念,它是一个运行着的程序,包含了进程的动态创建.调度和消亡的过程,是Linux的基本调度单位. 那么从系统的角度看如何描述并表示它的变化呢?在这里,是通过进程控制块(PCB)来描述的.进程控制块包含了进程的描述信息.控制信息以及资源信息,它是进程的一个静态描述. 内核使用进程来控制对CPU和其他系统资源的访问,并且使用进程来决定在CPU上运行哪个程

Linux进程控制编程

一.获取ID #include<sys/types.h> #include<unistd.h> pid_t getpid(void)    获取本进程ID pid_t getppid(void)  获取父进程ID 父进程:现有进程中,创建新的进程. 例:getpid.c #include<stdio.h> #include<unistd.h> #include<stdlib.h> int main() { printf("PID=%d\

Linux 下进程操作,----进程的创建与控制

---恢复内容开始--- 进程是一个程序一次执行的过程,是操作系统动态执行的基本单元. 进程的概念主要有两点:第一,进程是一个实体.每个进程都有自己的虚拟地址空间,包括文本区.数据区.和堆栈区.文本区域存储处理器执行的代码:数据区存储变量和动态分配的内存:堆栈区存储着活动进程调用的指令和本地变量.第二,进程是一个"执行中的程序",它和程序有本质区别.程序是静态的,它是一些保存在磁盘上的指令的有序集合:而进程是一个动态的概念,它是一个运行着的程序,包含了进程的动态创建.调度和消亡的过程,

linux进程(1)--进程运行的环境

linux进程(1)–进程运行的环境 标签(空格分隔): linux 以下内容来自<UNIX环境高级编程>读书笔记 前引 首先想想下面几个问题能不能解答: 当程序被执行的时候,main函数时如何被调用的? 程序在内存的存储空间布局是怎样的? 命令行参数时如何传递给新程序的?进程如何读取环境变量? 进程堆空间的使用 进程的终止方式 进程是程序执行的基本,进程即为程序执行的活动体.下面是进程在系统上运行的一些环境. 一.main函数 int main(int argc, char *argv[])

Linux进程控制(三)

1. 进程间打开文件的继承 1.1. 用fork继承打开的文件 fork以后的子进程自动继承了父进程的打开的文件,继承以后,父进程关闭打开的文件不会对子进程造成影响. 示例: #include <stdio.h> #include <fcntl.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> int main() { char szBuf[32] = {'\0

Linux进程控制——exec函数族

原文:http://www.cnblogs.com/hnrainll/archive/2011/07/23/2114854.html 1.简介 在Linux中,并不存在exec()函数,exec指的是一组函数,一共有6个,分别是: #include <unistd.h> extern char **environ; int execl(const char *path, const char *arg, ...); int execlp(const char *file, const char

Linux进程控制知识总结

目录 一:进程标识符(ID) 二:进程操作 2.1创建一个进程 2.2 fork函数出错情况 2.3创建一个共享空间的子进程 2.4退出程序 2.5设置进程所有者 三:执行程序 3.1 exec函数 3.2 执行解释器文件 3.3在程序中执行Shell命令 四:关系操作符 4.1等待进程退出 4.2 等待指定的进程 进程控制 -- 一步 一:进程标识符(ID) 进程ID是用来标识进程的编号,就像身份证一样.不同的进程有不同的ID,可以通过ID来查询进程.进程标识符的类型是pit_t,其本质是一个