概念:
进程号(PID):系统标识一个进程的正整数
父进程号(PPID):父进程的进程号
进程组号(PGID):进程组是一个或多个进程的集合。
会话:一个或多个进程组的集合。
控制终端的含义我不是很理解?
函数/命令:
ps aux:查看当前系统所有进程的基本属性
getpid():获取当前进程PID
getppid():获取父进程的PID
__pid_t getpgid (__pid_t __pid):获取指定进程的进程组号,输入为0表示获取当前进程组号
pid_t getpgrp(void):获取当前进程进程组号
int setpgid(pid_t pid, pid_t pgid):将某个进程加入到某个进程组
参数1:待修改进程组号的进程PID,0表示当前进程
参数2:新的进程组号,0表示进程组号与参数1相同
__pid_t getsid (__pid_t __pid):获取指定进程的会话号,0表示调用进程
__pid_t setsid (void):创建新会话。如果调用进程是一个进程组组长,函数返回错误。
pid_t tcgetpgrp(int filedes):filedes是打开的终端,获取与该终端相关联的前台进程组的进程组号
pid_t tcsetpgrp(int filedes, pid_t pgrpid):将控制终端filedes的前台进程组ID设置为pgrpid。pgrpid应该是同一会话中的进程组ID
pid_t tcgetsid(int filedes):获取控制终端会话首进程的会话ID
#include#include #include #include int main(){ int fd; pid_t pid; pid = fork(); if(pid == -1) perror("fork"); else if(pid > 0) { wait(NULL); exit(EXIT_FAILURE); } else { if((fd = open("/dev/tty1",O_RDWR)) == -1) //注意 tty1是在ps 查看进程中能够找到的 换成ps中找不到的会打不开 perror("open"); printf("pid=%d,ppid=%d\n", getpid(), getppid()); //获取进程号 父进程号 printf("sid=%d,tcgetsid=%d\n",getsid(getpid()), tcgetsid(fd)); //获取会话号 终端会话号 printf("tcgetpgrp=%d\n", tcgetpgrp(fd)); //获取前台进程进程组号 printf("pigd=%d\n",getpgid(getpid())); //获取当前进程进程组id }}
可以看到,进程组号,终端的前台进程组号都是当前进程的父进程号。
当前进程的会话号和终端的会话号相同。
进程用户属性
真实用户号(RUID):创建该进程的用户的UID
真实用户组号(RGID):RUID的用户所在的组号
有效用户号(EUID):多数情况下EUID与UID相同,如果可执行文件的setuid位有效,则其他用户运行该程序时EUID和UID不同,EUID为该文件的拥有者,UID为文件的使用者。表示实际拥有的权限。
有效用户组号(EGID):有效用户所在的用户组号
setuid位:当该位有效时,在执行该程序时,该执行用户所拥有的权限等同于文件的拥有者的权限。执行用户可以通过该权限处理其他的文件。
设置setuid位有效 chmod u+s 可执行文件名
设置setuid位无效 chmod u-s 可执行文件名
当设置成有效时,可执行位上显示s,否则显示x.
举例:修改密码时,需要修改/etc/passwd 文件,该文件权限如下。root用户可以读写,但普通用户只能读。
普通用户通过执行 /usr/bin/passwd 文件来修改密码。其权限如下,setuid位是有效的
这样,普通用户执行/usr/bin/passwd时,就有了相当于root的权限,而root可以写/etc/passwd,这样普通用户就可以修改密码了。如果把setuid位设为无效,则普通用户就无法修改密码。
getresuid() :分别获取真实的,有效的和保存过的用户标识号
#include#include #include int main(){ int uid, euid, suid; getresuid(&uid, &euid, &suid); printf("uid = %d, euid = %d, suid = %d\n", uid, euid, suid); return 0;}
可以看到,当root执行该文件时,使用id为0(root使用),有效用户id为0(有相当于root的权限),保存过的用户标识号为0(root)
当game执行该文件时,使用id为1000(game使用,这也是真实用户号),有效用户id为0(有相当于root的权限),保存过的用户标识号为0(root)
__uid_t getuid (void):获取进程真实用户号
__uid_t geteuid (void):获取有效用户号
__uid_t getgid(void):获取真实用户组号
__uid_t getegid(void):获取有效进程用户组号
linux命令:
id :查看当前用户的id信息
whoami :显示当前用户
进程管理及控制
1.创建进程
__pid_t fork (void)
成功,则父进程返回子进程的pid,子进程返回0. 子进程会复制父进程的几乎所有的信息。子进程在fork返回的位置继续向下执行。
失败,返回-1.
i.子进程对父进程文件流缓冲区的处理
#include#include #include int main(int argc, char *argv[]){ pid_t pid; printf("before fork, have enter\n"); //有回车,先输出 printf("before fork, no enter: pid = %d\t", getpid()); //没有回车,缓存到输出流缓冲区 pid = fork(); if(pid == 0) printf("\nchild, after fork: pid = %d\n", getpid()); else printf("\nparent, after fork: pid = %d\n", getpid());}
注意:没有回车时,内容先保存在了缓冲区里!
看到,子进程中复制了父进程缓冲区的内容!
ii.子进程对父进程打开的文件描述符的处理
共享!
#include#include #include #include int main(int argc, char *argv[]){ pid_t pid; int fd; int i = 1; int status; char *ch1 = "hello"; char *ch2 = "world"; char *ch3 = "IN"; if((fd = open("test.txt",O_RDWR|O_CREAT,0644)) == -1) { perror("parent open"); exit(EXIT_FAILURE); } if(write(fd,ch1,strlen(ch1)) == -1) { perror("parent write"); exit(EXIT_FAILURE); } if((pid = fork()) == -1) { perror("fork"); exit(EXIT_FAILURE); } else if(pid == 0) { i = 2; printf("in child\n"); printf("i = %d\n", i); if(write(fd, ch2, strlen(ch2)) == -1) perror("child write"); return 0; } else //父进程 { sleep(1); //等子进程先执行 printf("in parent\n"); printf("i = %d\n", i); if(write(fd, ch3, strlen(ch3)) == -1) perror("parent write"); wait(&status); //等待子进程结束 return 0; }}
可以看到,当文件打开后,父子进程写文件时共享了文件的偏移量等信息。不会出现覆盖。
iii.结合vfork测试全局数据段与BSS段使用策略
vfork():与fork基本相同,但是vfork()不复制父进程的地址空间,而是跟父进程共享所有资源。
#include#include #include #include #include int glob = 6; //全局已经初始化变量,位于数据段中int main(){ int var; pid_t pid; var = 88; //局部变量,位于栈空间中 printf("in beginning:\tglob=%d\tvar=%d\n",glob, var); if((pid = vfork()) < 0) { perror("vfork"); exit(EXIT_FAILURE); } else if(pid == 0) { printf("in child, modify the var: glob++, var++\n"); glob++; var++; printf("in child:\tglob=%d\tvar=%d\n",glob, var); _exit(0); //退出 } else { printf("in parent:\tglob=%d\tvar=%d\n",glob,var); return 0; }}
可以看到,父子进程打印的内容相同,说明他们共享资源。
如果把vfork改为fork,则,父子进程打印的内容不同。fork的子进程复制了父进程的内容。
iv.子函数调用vfork创建子进程
会出错!
#includevoid test(void) //test函数,在此函数中调用vfork{ pid_t pid; pid = vfork(); if(pid == -1) { perror("vfork"); exit(EXIT_FAILURE); } else if(pid == 0) { printf("1:child pid=%d,ppid=%d\n",getpid(),getppid()); return; } else printf("2:parent pid=%d,ppid=%d\n",getpid(),getppid());}void fun(void){ int i; int buf[100]; for(i = 0; i < 100; i++) buf[i]=0; printf("3:child pid=%d,ppid=%d\n",getpid(),getppid());}int main(void){ pid_t pid; test(); fun(); return 0;}
出错原因:
调用main,申请栈空间,没有问题。
调用test函数,里面用vfork创建新进程,共享栈空间。子进程先运行,没有问题。子进程执行完test后返回,清理栈空间
子进程调用fun(),覆盖原来的test函数的栈空间。子进程执行完毕
子进程退出后,父进程从vfork返回处执行代码,没有问题。但返回时该栈空间已经不存在,出现栈错误。
把代码里的vfork改为fork则代码可以正常运行!