学习APUE笔记4-进程基本知识

四、进程基本知识

已经进入多进程阶段

1、进程标识符pid

类型pid_t(传统意义上是16位有符号的整形数)

虚拟机计数pid号不够用

命令ps

ps axf

ps axm

ps ax -L 以linux特有的方式查看

ps -ef 可以查看系统中的所有进程

 

进程号是顺次向下使用

getpid获得当前进程的pid号:pid_t getpid(void);

getppid获得当前进程父进程号:pid_t getppid(void);      

 

2、进程的产生

 

fork();

子进程会把父进程的页表复制一份,因此子进程继承了父进程的代码和数据(当然也包括打开的文件、标准 io 的缓存区等),

但是并不复制 file 结构体(文件表),因此父子进程会使用同一个文件移,fork 采用写时拷贝技术,

无论父子进程谁对哪一页进行写操作都会把那一页拷贝一份,未处理的闹钟和未决信号子进程不继承。

 

注意理解关键字:duplicating,意味着拷贝,克隆,一模一样等含义。

fork后父进程的区别:

fork的返回值不一样,pid不一样,ppid也不同

未决信号和文件锁不继承,资源利用量清零。

 

init进程:1号,是所有进程的祖先进程。

fork之前要刷新所有的缓冲区的流用fflush。

输出到终端是行缓冲,输出到文件是全缓冲。

 

1>创建一个子进程:pid_t fork(void);

返回值:子进程返回0,父进程返回子进程ID;出错,返回-1.

(调度器的调度策略来决定那个那个进程先运行)

[fork实例1]

#include <stdio.h>

#include <stdlib.h>

#include <sys/types.h>

#include <unistd.h>

 

int main()

{

pid_t pid;

printf("[%d]:Begin!\n",getpid());//父进程ID

fflush(NULL); /*!!!*/

pid = fork();

if(pid < 0)

{

perror("fork()");//产生子进程

exit(1);

}

if(pid == 0) // child

{

printf("[%d]:Child is working!\n",getpid());

}

else // parent

{

printf("[%d]:Parent is working!\n",getpid());

}

printf("[%d]:End!\n",getpid());

sleep(10000);

exit(0);

}

 

孤儿进程:父进程先于子进程结束,子进程就变成孤儿进程,不过孤儿进程马上就会成为 init进程的子进程。

僵尸进程:如果子进程结束了而父进程没结束且父进程没有替子进程收尸,那么子进程就会变成僵尸进程,但如果父进程结束了,相应的僵尸进程也就没有了。

#include<stdio.h>

#include<stdlib.h>

#include<unistd.h>

 

#define LEFT 30000000

#define RIGHT 30000200

 

int main()

{

int i,j,mark;

pid_t pid;

for(i = LEFT; i <= RIGHT; i++)

{

pid = fork();

if(pid < 0)

{

perror("fork()");

exit(1);

}

if(pid == 0) // child

{

mark = 1;

for(j = 2 ; j <= i/2; j++)

{

if(i % j == 0)

{

mark = 0;

break;

}

}

if(mark)

printf("%d is a primer.\n",i);

//sleep(10000);//让父进程先结束,出现孤儿进程

exit(0);//结束做完运算的子进程

}

}

//sleep(10000);//让子进程先结束,出现僵尸进程

exit(0);

}

 

pid_t vfork(void);

vfork 函数创建的子进程和父进程在同一个地址空间运行,并且会保证子进程先执行(子进程执行完成后父进程才执行),一般用在子进程立即调用 exec 的场合。

注意:如果在子进程中修改了数据、调用了函数或者没有调用 exit 或者 exec 函数就返回可能会有不可预知的结果出现。

注意:某些平台已经把 vfork 弃用,因此该函数可能导致程序不可移植。

 

3、[进程的消亡及释放资源]

 

等待子进程状态的改变(收尸)

进程状态的改变可能是进程的终止(异常或者正常)、进程停止、进程恢复执行.

等待子进程结束可以获得子进程的终止状态,并且和以防止子进程变成僵尸进程.

 

1>pid_t wait(int *status);

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

1.如果没有子进程在运行,则出错返回

2.如果有子进程已经终止,则立即返回

3.如果所有子进程都在运行,则阻塞等待

 

获取进程状态的方法:

WIFEXITED    判断进程是否正常终止

WEXITSTATUS  如果进程正常终止,可用这个方法获得进程的返回值

WIFSIGNALED  判断进程是否被信号杀死

WTERMSIG     如果进程被信号杀死,可用这个方法获得杀死进程的信号

WCOREDUMP    如果进程被信号杀死,可用这个方法获得进程是否生成了 core 文件

WIFSTOPPED   判断进程是否被信号停止

WSTOPSIG     如果进程被信号停止,可以这个方法获得停止进程的信号

WIFCONTINUED 判断进程是否被信号唤醒继续执行

 

(wait等待收尸)

#include <stdio.h>

#include <stdlib.h>

#include <sys/types.h>

#include <sys/wait.h>

#include <unistd.h>

#define LEFT 30000000

#define RIGHT 30000200

int main()

{

int i,j,mark;

pid_t pid;

for(i = LEFT; i <= RIGHT; i++)

{

pid = fork();

if(pid < 0)

{

perror("fork()");

exit(1);

}

if(pid == 0) // child

{

mark = 1;

for(j = 2 ; j <= i/2; j++)

{

if(i % j == 0)

{

mark = 0;

break;

}

}

if(mark)

printf("%d is a primer.\n",i);

exit(0);

}

}

//父进程创建fork201个子进程,子进程跑,

//父进程等待201次到那201个子进程exit(0)回来,父进程释放子进程。

int st;

for(i = LEFT; i <= RIGHT; i++)

wait(NULL);//收尸  不关心状态

//  wait(&st);//关心状态

exit(0);

}

 

2>pid_t waitpid(pid_t pid, int *status, int options);

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

 

1.pid==-1 等待任何一个进程,等价于 wait

2.pid==0  等待和调用进程同组的进程(进程组 ID 相等)

3.pid>0   等待进程 ID 等于 pid 的进程

4.pid<0   等待进程 ID 等于|pid|的进程

 

options 参数:(位图)

WNOHANG    不阻塞

WUNTRACED  如果等待的进程在停止后恢复了执行,则返回其状态

WCONTINUED 如果等待的进程处于停止状态并且没有报告过,则返回其状态

 

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

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

 

1.type == P_PID  等待进程 ID 为 id 的进程

2.type == P_PGID 等待和调用进程同一组的进程

3.type == P_ALL  等待所有进程,忽略 id

 

options 选项值:

WEXITED    等待终止的子进程

WSTOPPED   等待被信号停止的子进程

WCONTINUED 等待被信号恢复执行的子进程

WNOHANG    不阻塞

WNOWAIT    把进程放在一个可以 wait 的状态,下一次 wait 还可以获得该进程的状态

 

如果子进程需要做比较耗时的任务,但是父进程又不想等待子进程结束而且不能产生僵尸进程,

方法是 fork 两次,爷进程用 wait 等待,父进程调 exit 退出,孙进程做比较耗时的任务,

这样爷进程的 wait 很快就返回,这样孙进程就成为了 init 进程的子进程

 

竞争条件:多个进程同时操作共享资源时会有竞争,有竞争就可能存在错误.

 

N个进程筛质数

1、分块

2、交叉分配

3、池

(创建3个子进程运用交叉分配实例)

#include <stdio.h>

#include <stdlib.h>

#include <sys/types.h>

#include <sys/wait.h>

#include <unistd.h>

#define LEFT 30000000

#define RIGHT 30000200

#define N 3

int main()

{

int i,j,mark,n;

pid_t pid;

for(n = 0 ; n < N; n++)

{

pid = fork();

if(pid < 0)

{

perror("fork()");

exit(1);

}

if(pid == 0)

{

for(i = LEFT+n; i <= RIGHT; i+=N)

{

mark = 1;

for(j = 2 ; j <= i/2; j++)

{

if(i % j == 0)

{

mark = 0;

break;

}

}

if(mark)

printf("[%d]%d is a primer.\n",n,i);

}

exit(0);

}

}

for(n = 0 ; n < N; n++)

wait(NULL);

exit(0);

}

/*

[2]30000023 is a primer.

[1]30000001 is a primer.

[2]30000041 is a primer.

[1]30000037 is a primer.

[1]30000049 is a primer.

[2]30000059 is a primer.

[1]30000079 is a primer.

[2]30000071 is a primer.

[1]30000109 is a primer.

[2]30000083 is a primer.

[1]30000133 is a primer.

[2]30000137 is a primer.

[1]30000163 is a primer.

[2]30000149 is a primer.

[1]30000169 is a primer.

[2]30000167 is a primer.

[1]30000193 is a primer.

[1]30000199 is a primer.

*/

 

4、exec函数族

执行新程序:

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[]);

int execvpe(const char *file, char *const argv[], char *const envp[]);

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

注意:file 可以传递路径名也可是文件名,如果是文件名则按照 PATH 环境变量去寻找命令新程序的地址空间会覆盖旧程序的地址空间,

新程序会继承 PID,PPID,UID,EUID(如果是一个非设置用户 ID 程序)等。

 

(exacl实现data+%s相当于大时戳)

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

// date +%s

int main()

{

puts("Begin!");

fflush(NULL); /*!!!使用execl之前应刷新所有的缓冲流*/

//新进程映像替换旧得旧进程映像

execl("/bin/date","date","+%s",NULL);

perror("execl()");

exit(1);

 

puts("End!");

exit(0);

}

/*

Begin!

1505125257(时戳)

*/

 

自己替换成别人,不如一开始就是别人,因为pid一直未改变。

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#uinclude <sys/wait.h>

// date +%s

int main()

{

pid_t pid;

 

puts("Begin!");

fflush(NULL); /*!!!*/

 

pid = fork();

if(pid < 0)

{

perror("fork()");

exit(1);

}

if(pid == 0)

{

execl("/bin/date","date","+%s",NULL);

perror("execl()");

exit(1);

}

wait(NULL);

 

puts("End!");

exit(0);

}

/*

Begin!

1505125821

End!

*/

 

(execl实现sleep)

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

// sleep 100

int main()

{

pid_t pid;

puts("Begin!");

fflush(NULL); /*!!!*/

pid = fork();

if(pid < 0)

{

perror("fork()");

exit(1);

}

if(pid == 0)

{

execl("/bin/sleep","sleep","100",NULL);

perror("execl()");

exit(1);

}

wait(NULL);

puts("End!");

exit(0);

}

 

(myshell)

#include <stdio.h>

#include <stdlib.h>

#include <glob.h>

#include <unistd.h>

#include <string.h>

 

#define DELIMS " \t\n"

 

struct cmd_st

{

glob_t globres;

};

 

void prompt(void)

{

printf("[mysh-0.1]$ ");

}

 

int parse(char *line,struct cmd_st *res)

{// "ls     -l      -a       /etc"  ->  "ls" "-l"  "-a"  

char *tok;

int i = 0;

 

while(1)

{

tok = strsep(&line,DELIMS);

if(tok == NULL)

break;

if(tok[0] == '\0')

continue;

 

glob(tok,GLOB_NOCHECK|GLOB_APPEND*i,NULL,&res->globres);

i = 1;

}

return 0;

}

 

int main()

{

char *line = NULL;

size_t linesize = 0;

struct cmd_st cmd;

pid_t pid;

 

while(1)

{

prompt();//打印提示符

 

if(getline(&line,&linesize,stdin) < 0)//从输入流中读入一行

break;

 

// line-> ls   -l   -a   /etc

 

parse(line,&cmd);//解析

 

if(0/*内部命令*/)

{ }

else /*外部命令*/

{

pid = fork();

if(pid < 0)

{

perror("fork()");

exit(1);

}

 

if(pid == 0)

{

execvp(cmd.globres.gl_pathv[0],cmd.globres.gl_pathv );

perror("execvp()");

exit(1);

}

wait(NULL);

}

}

 

//globfree();

 

exit(0);

}

 

5、用户权限及组权限

u+s

g+s

获取用户身份:

 

uid_t getuid(void);

返回值:返回调用进程的实际用户ID。

 

uid_t geteuid(void);

返回值:返回调用进程的有效用户ID。

 

获取组身份:

 

gid_t getgid(void);

返回值:返回调用进程的实际组ID。

 

gid_t getegid(void);

返回值:返回调用进程的有效组ID。

 

设置实际用户ID和有效用户ID:int setuid(uid_t uid);

设置实际组ID和有效组ID:int setgid(gid_t gid);

返回值:成功,返回0;出错,返回-1.

 

交换实际用户ID和有效用户ID值:

int setreuid(uid_t ruid, uid_t euid);

int setregid(gid_t rgid, gid_t egid);

返回值:成功,返回0;出错,返回-1.

 

只更改有效用户ID和有效用户ID:

int seteuid(uid_t uid);

int setegid(gid_t gid);

返回值:成功,返回0;出错,返回-1.

 

6、观摩课:解释器文件

解释器文件(脚本文件)

exec 函数支持解释器文件

execl(“/aaa/bbb/ccc.sh”, “a0”, “a1”, NULL);

一般的解释器文件格式为 #!/xxx/yyy arg0 arg1 ...

当 exec 执行的是一个解释器文件时,其实并不执行文件本身,而是执行#!/xxx/yyy

yyy 程序从命令行得到的参数是

argv[0]= /xxx/yyy

argv[1]=arg0

argv[2]=arg1

...

argv[n]= /aaa/bbb/ccc.sh

argv[n+1]=a1

 

7、system

函数 system = fork + exec + wait

在代码中用 system 函数可以执行 shell 命令

执行一个shell命令:int system(const char *command);

注意:system 是 fork 然后用一个新的 shell 去执行 cmmand

 

#include <stdio.h>

#include <stdlib.h>

 

int main()

{

system("date +%s > /tmp/out");

 

exit(0);

}

 

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

 

// system -> date +%s

 

int main()

{

pid_t pid;

 

puts("Begin!");

 

fflush(NULL); /*!!!*/

 

pid = fork();

if(pid < 0)

{

perror("fork()");

exit(1);

}

if(pid == 0)

{

execl("/bin/sh","sh","-c","date +%s",NULL);

perror("execl()");

exit(1);

}

wait(NULL);

 

puts("End!");

 

exit(0);

}

 

 

8、进程会计

作用:记录每一个进程的结束。

进程会计记录了什么时间启动了什么进程,进程的属主是什么,进程的结束时间等信息。

acct:启用和禁止进程会计。

开关进程会计:int acct(const char *filename);

返回值:成功,返回0;出错,返回-1并设置errno.

复制代码

 

会计记录结构定义在头文件<sys/acct.h>中(Linux中,该头文件在/usr/include目录下),其形式如下:

typedef    u_short    comp_t;    /* 3-bit base 8 exponent; 13-bit fraction */

 

struct acct

{

    char    ac_flag;        /* flag */

    char    ac_stat;        /* termination status (signal & core flag only) */

                            /* (Solaris only) */

    uid_t    ac_uid;        /* real user ID */

    gid_t    ac_gid;        /* real group ID */

    dev_t    ac_tty;        /* controlling terminal */

    time_t   ac_btime;      /* starting calendar time */

    comp_t   ac_utime;      /* user CPU time (clock ticks) */

    comp_t   ac_stime;      /* system CPU time (clock ticks) */

    comp_t   ac_etime;      /* elapsed time (clock ticks) */

    comp_t   ac_mem;        /* average memory usage */

    comp_t   ac_io;         /* bytes transferred (by read and write) */

                            /* "blocks" on BSD system */

    comp_t   ac_rw;         /* blocks read or written */

                            /* (not present on BSD system) */

    char     ac_comm[8];    /* command name: [8] for Solaris, */

                            /* [10] for Mac OS X, [16] for FreeBSD, and */

                            /* [17] for Linux */   

};

 

9、进程时间

times()获取自己以及已终止进程的值(墙上时钟时间、用户CPU时间和系统CPU时间)

clock_t times(struct tms *buf);

返回值:成功,返回的是真实时间,buf中返回系统时间和用户时间(以时钟滴答数为单位);出错,返回-1.

buf指向的tms结构体:

struct tms {

            clock_t tms_utime;  /* user time */

            clock_t tms_stime;  /* system time */

            clock_t tms_cutime; /* user time of children */

            clock_t tms_cstime; /* system time of children */

           };

 

注意:如果要测试一段代码的执行时间,应该在开始调用一次 times 结束之后再调用times一次,取两次的差值便是程序的执行时间,

但是 times 获取到的时间都是时钟嘀嗒数,需要除以 sysconf(_SC_CLK_TCK)才能换算成秒.

 

10、守护进程

Linux Daemon(守护进程)是运行在后台的一种特殊进程。

它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。

会话session,标识sid

终端

 

会话

登陆系统时候会创建一个会话(始于用户登录,终于用户退出)

Linux 下每打开一个终端都会创建一个会话

一个会话是多个进程组的组合

 

获得进程的会话ID:pid_t getsid(pid_t pid);

建立一个新会话:pid_t setsid(void);

1.调用进程变成会话首进程

2.调用进程成为组长进程

3.调用进程即将没有控制终端(收不到终端的 crtl+c 信号)

注意:组长进程不能建立新会话,新会话的首进程一定是组长进程

注意:父进程结束,子进程变为后台进程组

 

编写守护进程的一般步骤步骤:

(1)在父进程中执行fork并exit推出;

(2)在子进程中调用setsid函数创建新的会话;

(3)在子进程中调用chdir函数,让根目录 ”/” 成为子进程的工作目录;

(4)在子进程中调用umask函数,设置进程的umask为0;

(5)在子进程中关闭任何不需要的文件描述符

Linux中的进程与控制终端,登录会话和进程组之间的关系:进程属于一个进程组,

进程组号(GID)就是进程组长的进程号(PID)。登录会话可以包含多个进程组。

这些进程组共享一个控制终端。这个控制终端通常是创建进程的登录终端。

setsid();

getpgrp();

getpgid();

setpgid();

 

进程组:同一个进程组中的进程的进程组 ID 一样,同一个进程组中的进程接收来自同一终端的各种信号.

 

用管道连接到一起的进程在一个进程组

cat /dev/sda >/dev/null | cat /dev/sda >/dev/null

刚 fork 出来的子进程和父进程在同一个组

获得进程的进程组 ID

pid_t getpgrp(void);

/* POSIX.1 version */

pid_t getpgid(pid_t pid);

/* SUS */

设置进程的进程组 ID

int setpgrp(void);

int setpgid(pid_t pid, pid_t pgid);

/* System V version */

注意:查看进程 ps -eo comm,pid,ppid,pgid

注意:一个进程只能设置自己或者子进程的进程组 ID

注意:由 bash shell 启动的新进程是一个独立的进程组(和 bash 不是同一个组)

单实例守护进程:锁文件 /var/run/name.pid

启动脚本文件:/etc/rc*   ->local

 

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <syslog.h>

#include <errno.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#define FNAME "/tmp/out"

int deamonize()

{

pid_t pid;

int fd;

pid = fork();

if(pid < 0)

{

syslog(LOG_ERR,"fork():%s",strerror(errno));

return -1;

}

if(pid > 0) //parent

exit(0);

// child

// close(0);

// close(1);

// close(2);

fd = open("/dev/null",O_RDWR);

if(fd < 0)

{

syslog(LOG_WARNING,"open():%s",strerror(errno));

}

else

{

dup2(fd,0);

dup2(fd,1);

dup2(fd,2);

}

setsid();//守护进程

umask(0);

chdir("/");

return 0;

}

 

int main()

{

FILE *fp;

int i;

openlog("MyDeamon",LOG_PID,LOG_DAEMON);

if(deamonize() != 0)

{

syslog(LOG_ERR,"deamonize() failed.");

exit(1);

}

else

syslog(LOG_INFO,"deamonize() successed.");

/*do sth*/

fp = fopen(FNAME,"w");

if(fp == NULL)

{

syslog(LOG_ERR,"fopen():%s",strerror(errno));

exit(1);

}

syslog(LOG_DEBUG,"%s was opened.",FNAME);

for(i = 0 ; ; i++)

{

fprintf(fp,"%d\n",i);

fflush(fp);

syslog(LOG_DEBUG,"%d was printed.",i);

sleep(1);

}

fclose(fp);

closelog();

exit(0);

}

 

11、系统日志

日志对任何一个OS、应用软件、服务进程而言都是必不可少的模块。日志文件对于系统和网络安全起到中大作用,同时具有审计、跟踪、排错功能。

可以通过日志文件监测系统与网络安全隐患,以及监测黑客入侵攻击路线。

/var/log

主日志:messages

权限分割层

syslogd服务

将内容交给syslogd,syslogd统一写。

ps axj | grep “syslogd”查看当前syslogd。

openlog();

syslog();

sysclose();

 

原理:通常,syslog守护进程读取三种格式的记录消息。此守护进程在启动时读一个配置文件。

一般来说,其文件名为/etc/syslog.conf

(注释:if you want to redirect log to other place,you need to change this),

该文件决定了不同种类的消息应送向何处。例如,紧急消息可被送向系统管理员(若已登录),

并在控制台上显示,而警告消息则可记录到一个文件中。

 

void openlog(const char *ident, int option, int facility);

调用 openlog() 函数改变你的日志消息的呈现方式。这允许你设置一个字符串--ident,

这将被加在你的日志消息的最前面。你可以使用这些来指示哪一个程序正在创建这条消息。

facility 参数记录了一个默认的程序标识码---这在后来使用 syslog() 里会被用到。

默认的 facility 值为 LOG_USER. logopt 参数配置将来要调用 syslog() 函数的行为。

它是0个或多个一下参数的OR运算:

logopt Parameter                     Description

LOG_PID                              加上进程标识符(系统分配给每个进程的一个独一无二的数字标识)

LOG_CONS                             如果消息无法记录到日志文件里则发送消息到控制台

LOG_NDELAY                           在第一次调用 syslog 函数时打开日志功能

LOG_NDELAY                           立即打开日志功能,而不等到第一次记日志时

 

openlog() 函数将分配和打开一个用来写到日志功能的文件描述符,

你也可以通过调用 closelog() 函数来关闭它。

注意,你无须在调用 syslog() 之前调用 openlog() ,

这是因为如果有需要的话 syslog 它自己也会打开日志功能。

void syslog(int priority, const char *format, ...);

syslog() 函数向日志设备(日志工具 facility)发送日志消息。

每一个消息都有一个 priority(优先级) 参数。

以下是危险等级的降序排列:

Priority Level                      Description

LOG_EMERG                           紧急状况

LOG_ALERT                           高优先级故障,如数据库损坏

LOG_CRIT                            关键性(critical)错误,如硬件操作失败

LOG_ERR                             一般错误

LOG_WARNING                         一般警告

LOG_NOTICE                          需要注意的特殊条件

LOG_INFO                            通知性消息(Information messages)

LOG_DEBUG                           调试消息

 

void closelog(void);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值