分类: LINUX
2009-09-11 11:17:54
-----------------------------------------------------------
本文系本站原创,欢迎转载!
转载请注明出处:http://sjj0412.cublog.cn/
-----------------------------------------------------------
会话是主要是针对控制终端来说的,当设置为一个新会话时,这个会话将失去控制终端,即没有tty,然后要重新设置控制终端,控制终端又是什么呢,控制终端一般是控制会话中的进程的,比如输入,输出,当输入ctrl+c等时,杀死所有前台进程组中的进程等操作,所以一个会话就是一个控制体,当会话leader退出时,会释放tty。
代码如下:
exit(){
if (current->leader)
disassociate_ctty(1);
}
/*
* This function is typically called only by
the session leader, when
* it wants to dissassociate itself from its
controlling tty.
*
* It performs the following functions:
* (1) Sends a SIGHUP and SIGCONT to the foreground
process group
* (2) Clears the tty from being controlling the
session
* (3) Clears the controlling tty for all processes
in the
* session
group.
*/
void disassociate_ctty(int priv)
{
struct tty_struct *tty;
struct task_struct *p;
if (current->tty >= 0) {
tty = tty_table[current->tty];
if (tty) {
if (tty->pgrp > 0) {
kill_pg(tty->pgrp, SIGHUP, priv);
kill_pg(tty->pgrp, SIGCONT, priv);
}
tty->session = 0;
tty->pgrp = -1;
} else
printk("disassociate_ctty: ctty is
NULL?!?");
}
for_each_task(p)
if (p->session == current->session)
p->tty = -1;
}
当一个session-leader程序退出时才会将ttyp->session=0,也就是说tty才空闲,其他的
当一个会话leader第一次打开一个没有被占用的终端时,这个终端就成为了控制终端。
tty_open(){
…..................
if (!noctty &&
current->leader &&
current->tty<0 &&
tty->session==0) {
current->tty = minor;
tty->session =
current->session;
tty->pgrp = current->pgrp;
…........................
}
从上面可以看出,只有ttyx没有被占用 ,且当前进程没有tty,才会有用。
同时可以通过tty_ioctl释放终端。
tty_ioctl(){
case TIOCNOTTY:
if (current->tty != dev)
return -ENOTTY;
if (current->leader)
disassociate_ctty(0);
current->tty = -1;
return 0;
}可以释放终端。
下面以测试代码验证:
if(fork()>0)
exit(0);
if(setsid()<0)
printf("setsid
err\n");
if(fork()>0){
int fd;
if((fd=open("/dev/tty1",O_RDWR))>=0){
if((fd=open("/dev/tty",O_RDWR))>=0)
printf("leader:tty after
tty1\n");
}
close(fd);
if((fd=open("/dev/ttyS1",O_RDWR))>=0){
if((fd=open("/dev/tty",O_RDWR))>=0)
printf("leader:tty after ttyS1\n");
}
close(fd);
}
else {
int fd;
if((fd=open("/dev/tty1",O_RDWR))>=0){
if((fd=open("/dev/tty",O_RDWR))>=0)
printf("others:tty after tty1\n");
}
close(fd);
if((fd=open("/dev/ttyS1",O_RDWR))>=0){
if((fd=open("/dev/tty",O_RDWR))>=0)
printf("others:tty after
ttyS1\n");
}
close(fd);
}
jerry@jerry-laptop:~/test$
./newtask
leader:tty after ttyS1
从上看出只有leader,打开非占用的ttyx才会将tty->ttyx;
if((fd=open("/dev/ttyS1",O_RDWR))>=0){
//默认是ctty的,只要满足/dev/ttyx没有被占用,且必须是session-leader。
这个语句后/dev/tty就不错错了,即current->tty有了 if((fd=open("/dev/tty",O_RDWR))>=0)
printf("leader:tty after ttyS1\n");
}
close(fd);
所以只要是leader,通过打开一个tty,占用控制终端。
通过setsid,变成一个session,就和原来的进程脱离关系了,包括没有tty,变成一个独立的进程组.
为什么要有tty来控制,而不用标准输入,输出来表示,这是因为标准输入,输入出是文件级别,而tty是设备级别,一般是先有控制终端后有标准输入,输出,这个是为了方便用户。
一般是setsid后,
setsid();
int fd=open(“/dev/ttyx”);//tty设置为这个了,同时标准输入
dup2(fd,0);//标准输出。
dup2(fd,1);//标准输入。
dup2(fd,2)//错误输出
控制终端,终端,控制台终端,当前终端:
控制终端是和会话结合一起的,控制终端其实必须是一个终端,它的设备文件是/dev/tty,也叫进程的控制终端,终端是指一切通过regesiter_tty注册的设备,当前终端/dev/tty0指向console_driver.tty[x](/dev/tty(1-n)),控制台终端是/dev/console,它是console_drivers[x].tty[x],在pc中console_drivers[x]包含console.driver,所以在pc中,/dev/console=/dev/tty0。
一个会话只能有一个前台进程组,可以有多个后台进程组.
这个一般是以shell来组织的,且在支持作业控制的内核里才有。
前台进程组,后台进程组通过fg,bg来切换。
void fg(char *p)
{
int pid=0;
if(p!=NULL)
{
pid=atol(p);
kill(pid,SIGSTOP);
kill(pid,SIGCONT);
waitpid(pid,NULL,NULL);
pid_jobs[jobs_num--]=0;
}
else
printf("error \n");
return 0;
}
void bg(char *p)
{
int pid=0;
if(p!=NULL)
{
pid=atol(p);
kill(pid,SIGCONT);
pid_jobs[jobs_num--]=0;
}
else
printf("error \n");
return 0;
}
可见,后台运行与前台运行的一个区别是:
前台运行时shell等待子进程的退出而阻塞父进程操作。而后台运行时,可以在父进程中输入命令继续其他操作。本质上没有区别,都是给子进程发送SIGCONT信号。
还有一个区别是终端操作:
终端驱动程序必须处理与作业控制有关的另一种情况。我们可以有一个前台作业,若干个后台作业,这些作中哪一个接收我们在终端上键入的字符呢?只有前台作业接收终端输入。如果后台作业试图读终端,那么这并不是一个错误,但是终端驱动程序检测这种情况,并且发送一个特定信号SIGTTIN给后台作业。这通常会停止此后台作业,而有关用户则会得到这种情况的通知,然后我们就可将此作业转为前台作业运行,于是它就可读终端。下列操作过程
显示了这一点:
$cat>temp foo &在后台启动,但将从标准输入读
[1 1681
$ 键入回车
[1]+Stopped(tty input)
cat>temp foo &
$ fg %1使1号作业成为前台作业
cat>temp foo shell告诉我们现在哪一个作业在前台
hello,world 输入1行?
^D 键入我们的文件结束符?
$ cat temp foo 键入我们的文件结束符
hello,world 检查该行已送入文件
shell在后台起动cat进程,但是当cat试图读其标准输入(控制终端)时,终端驱动程序知道它是个后台作业,于是将SIGTTIN信号送至该后台作业。shell检测到其子进程的状态改变,并通知我们该作业已被停止。然后,我们用shell的fg命令将此停止的作业送入前台运行。(关于作业控制命令,例如fg和bg的详细情况,以及标识不同作业的各种方法请参阅有关shell的手册页。)这样做使shell将此作业转为前台进程组(tcsetpgrp),并将继续信号(SIGCONT)送给该进程组。因为该作业现在前台进程组中,所以它可以读控制终端。
如果后台作业输出到控制终端又将发生什么呢?这是一个我们可以允许或禁止的选择项。通常,我们可以用stly(1)命令改变这一选择项。(第十一章将说明在程序中如何改变
这一选择
项)。下面显示了这种操作过程:
$cat temp foo & 在后台执行
[1]1719在提示符后出现后台作业的输出
[1]+Done cat temp foo &
$ stty tostop 使后台作业可能向控制终端输出
$ cat temp foo & 在后台再次执行?
那么终端驱动如何知道前台进程组,后他进程组的呢?
其实因为一个会话的控制终端只有一个前台进程组,故终端驱动记住前台进程组的gip即可,那如何让终端驱动知道呢?
这个可以通过tcgetpgrp和tcsetpgrp函数来实现。
tcgetpgrp和tcsetpgrp函数:
应用程序需要有一种方法用其可以通知系统核那一个进程组是前台进程组,这样,终端设备程序就能了解将终端输入和终端产生的信号送到何处(图9.7)。
#include
#include
pidt cgetpgrp(intfiledes);
Returns;processgroupID
返回:若成功为前台进程组ID,出错为-1
返回:若成功为0,出错为-1
函数tcgetpgrp返回前台进程组ID,它与在filedes上打开的终端相关。如果进程有一个控制终端,则该进程可以调用tcsetpgrp将
前台进程组ID设置为pgrpid。pgrpid值应当是在同一回对话期中的一个进程组的ID。Filedes必须引用该对话期的控制终端。
其实tcgetpgrp,tcsetpgrp最终调用的还是filedes对应的tty的tty_ioctl函数:
case
TIOCGPGRP:
//获得和控制终端关联的前台进程组号。
retval =
verify_area(VERIFY_WRITE, (void *) arg,
sizeof (pid_t));
if (retval)
return retval;
if (current->tty !=
termios_dev)
return -ENOTTY;
put_fs_long(termios_tty->pgrp,
(pid_t *) arg);
return 0;
case TIOCSPGRP:
//设置和和控制终端关联的前台进程组号
retval =
check_change(termios_tty, termios_dev);
if (retval)
return retval;
if ((current->tty <
0) ||
(current->tty != termios_dev) ||
(termios_tty->session !=
current->session))
return -ENOTTY;
pgrp
= get_fs_long((pid_t *) arg);
if (pgrp < 0)
return -EINVAL;
if (session_of_pgrp(pgrp)
!= current->session)
return -EPERM;
termios_tty->pgrp
= pgrp;
return 0;
这样终端驱动程序就知道了前台进程组。
既然讲到会话了,标准输出函数也讲下:
内核:
printk(“”);是通stdout打印的。
Void
printk(char *fmt, ...)
{
va_list args;
int n;
va_start(args, fmt);
n = vsprintf(sprint_buf, fmt, args);
va_end(args);
writestring(stdout,
sprint_buf, n);
//就是使用标准输出。
}
int
printf(char
*fmt, ...)
{
va_list args;
int n;
va_start(args, fmt);
n = vsprintf(sprint_buf, fmt, args);
va_end(args);
writestring(stdout,
sprint_buf, n);
return n;
}
gcc:
#define
printf _IO_printf
int
_IO_printf
#ifdef
__STDC__
(const char* format, ...)
#else
(format,
va_alist) char *format; va_dcl
#endif
{
int ret;
va_list args;
_IO_va_start(args, format);
ret = _IO_vfprintf(_IO_stdout, format, args);
va_end(args);
return ret;
}
#define
_IO_stdin ((_IO_FILE*)(&_IO_stdin_))
#define
_IO_stdout ((_IO_FILE*)(&_IO_stdout_))
#define
_IO_stderr ((_IO_FILE*)(&_IO_stderr_))
验证:
printf("clear
before\n");
int
fd = open("/dev/null", O_RDWR);//打开空洞文件.
if ( fd < 0 )
return -1;
if ( dup2( fd, 0 ) < 0 || //使用字符设备/dev/null的fops函数操作集,替换0句柄对应的文件操作集.
dup2( fd, 1 ) < 0 || //使用字符设备/dev/null的fops函数操作集,替换1句柄对应的文件操作集.
dup2( fd, 2 ) < 0 ) //使用字符设备/dev/null的fops函数操作集,替换2句柄对应的文件操作集.
{
close(fd);
return -1;
}
printf("clear after");
close(fd);//关闭打开的/dev/null
上面只输出clear before.